Hệ thống xếp lịch học tín chỉ cho sinh viên CNTT trên PHP & MySQL
111.067 lượt xem;
- datalist.php
- project /
1 <?php
2
3 define('datalist_filters_count', 20);
4 define('datalist_image_uploads_exist', false);
5 define('datalist_max_records_multi_selection', 1000);
6 define('datalist_max_page_lump', 50);
7 define('datalist_max_records_dv_print', 100);
8 define('datalist_auto_complete_size', 1000);
9 define('datalist_date_separator', '/');
10 define('datalist_date_format', 'mdY');
11
12 $curr_dir = dirname(__FILE__);
13 require_once($curr_dir . '/combo.class.php');
14 require_once($curr_dir . '/data_combo.class.php');
15 require_once($curr_dir . '/date_combo.class.php');
16
17 class DataList{
18 // this class generates the data table ...
19
20 var $QueryFieldsTV,
21 $QueryFieldsCSV,
22 $QueryFieldsFilters,
23 $QueryFieldsQS,
24 $QueryFrom,
25 $QueryWhere,
26 $QueryOrder,
27 $filterers,
28
29 $ColWidth, // array of field widths
30 $DataHeight,
31 $TableName,
32
33 $AllowSelection,
34 $AllowDelete,
35 $AllowMassDelete,
36 $AllowDeleteOfParents,
37 $AllowInsert,
38 $AllowUpdate,
39 $SeparateDV,
40 $Permissions,
41 $AllowFilters,
42 $AllowSavingFilters,
43 $AllowSorting,
44 $AllowNavigation,
45 $AllowPrinting,
46 $HideTableView,
47 $AllowCSV,
48 $CSVSeparator,
49
50 $QuickSearch, // 0 to 3
51
52 $RecordsPerPage,
53 $ScriptFileName,
54 $RedirectAfterInsert,
55 $TableTitle,
56 $PrimaryKey,
57 $DefaultSortField,
58 $DefaultSortDirection,
59
60 // Templates variables
61 $Template,
62 $SelectedTemplate,
63 $TemplateDV,
64 $TemplateDVP,
65 $ShowTableHeader, // 1 = show standard table headers
66 $TVClasses,
67 $DVClasses,
68 // End of templates variables
69
70 $ContentType, // set by DataList to 'tableview', 'detailview', 'tableview+detailview', 'print-tableview', 'print-detailview' or 'filters'
71 $HTML; // generated html after calling Render()
72
73 function __construct(){ // PHP 7 compatibility
74 $this->DataList();
75 }
76
77 function DataList(){ // Constructor function
78 $this->DataHeight = 150;
79
80 $this->AllowSelection = 1;
81 $this->AllowDelete = 1;
82 $this->AllowInsert = 1;
83 $this->AllowUpdate = 1;
84 $this->AllowFilters = 1;
85 $this->AllowNavigation = 1;
86 $this->AllowPrinting = 1;
87 $this->HideTableView = 0;
88 $this->QuickSearch = 0;
89 $this->AllowCSV = 0;
90 $this->CSVSeparator = ",";
91 $this->HighlightColor = '#FFF0C2'; // default highlight color
92
93 $this->RecordsPerPage = 10;
94 $this->Template = '';
95 $this->HTML = '';
96 $this->filterers = array();
97 }
98
99 function showTV(){
100 if($this->SeparateDV){
101 $this->HideTableView = ($this->Permissions[2]==0 ? 1 : 0);
102 }
103 }
104
105 function hideTV(){
106 if($this->SeparateDV){
107 $this->HideTableView = 1;
108 }
109 }
110
111 function set_headers(){
112 @header('Content-Type: text/html; charset=' . datalist_db_encoding);
113 @header('X-Frame-Options: SAMEORIGIN'); // prevent iframing by other sites to prevent clickjacking
114 }
115
116 function Render(){
117 // get post and get variables
118 global $Translation;
119
120 $adminConfig = config('adminConfig');
121
122 $FiltersPerGroup = 4;
123 $buttonWholeWidth = 136;
124
125 $current_view = ''; /* TV, DV, TVDV, TVP, DVP, Filters */
126
127 $Embedded = intval($_REQUEST['Embedded']);
128 $AutoClose = intval($_REQUEST['AutoClose']);
129
130 $SortField = $_REQUEST["SortField"];
131 $SortDirection = $_REQUEST["SortDirection"];
132 $FirstRecord = $_REQUEST["FirstRecord"];
133 $ScrollUp_y = $_REQUEST["ScrollUp_y"];
134 $ScrollDn_y = $_REQUEST["ScrollDn_y"];
135 $Previous_x = $_REQUEST["Previous_x"];
136 $Next_x = $_REQUEST["Next_x"];
137 $Filter_x = $_REQUEST["Filter_x"];
138 $SaveFilter_x = $_REQUEST["SaveFilter_x"];
139 $NoFilter_x = $_REQUEST["NoFilter_x"];
140 $CancelFilter = $_REQUEST["CancelFilter"];
141 $ApplyFilter = $_REQUEST["ApplyFilter"];
142 $Search_x = $_REQUEST["Search_x"];
143 $SearchString = (get_magic_quotes_gpc() ? stripslashes($_REQUEST['SearchString']) : $_REQUEST['SearchString']);
144 $CSV_x = $_REQUEST["CSV_x"];
145 $Print_x = $_REQUEST['Print_x'];
146 $PrintTV = $_REQUEST['PrintTV'];
147 $PrintDV = $_REQUEST['PrintDV'];
148 $SelectedID = (get_magic_quotes_gpc() ? stripslashes($_REQUEST['SelectedID']) : $_REQUEST['SelectedID']);
149 $insert_x = $_REQUEST['insert_x'];
150 $update_x = $_REQUEST['update_x'];
151 $delete_x = $_REQUEST['delete_x'];
152 $SkipChecks = $_REQUEST['confirmed'];
153 $deselect_x = $_REQUEST['deselect_x'];
154 $addNew_x = $_REQUEST['addNew_x'];
155 $dvprint_x = $_REQUEST['dvprint_x'];
156 $DisplayRecords = (in_array($_REQUEST['DisplayRecords'], array('user', 'group')) ? $_REQUEST['DisplayRecords'] : 'all');
157 list($FilterAnd, $FilterField, $FilterOperator, $FilterValue) = $this->validate_filters($_REQUEST, $FiltersPerGroup);
158 $record_selector = array();
159 if(isset($_REQUEST['record_selector']) && is_array($_REQUEST['record_selector']))
160 $record_selector = $_REQUEST['record_selector'];
161
162
163 $mi = getMemberInfo();
164
165 // validate user inputs
166 if(!preg_match('/^\s*[1-9][0-9]*\s*(asc|desc)?(\s*,\s*[1-9][0-9]*\s*(asc|desc)?)*$/i', $SortField)){
167 $SortField = '';
168 }
169 if(!preg_match('/^(asc|desc)$/i', $SortDirection)){
170 $SortDirection = '';
171 }
172
173 if(!$this->AllowDelete){
174 $delete_x = '';
175 }
176 if(!$this->AllowDeleteOfParents){
177 $SkipChecks = '';
178 }
179 if(!$this->AllowInsert){
180 $insert_x = '';
181 $addNew_x = '';
182 }
183 if(!$this->AllowUpdate){
184 $update_x = '';
185 }
186 if(!$this->AllowFilters){
187 $Filter_x = '';
188 }
189 if(!$this->AllowPrinting){
190 $Print_x = '';
191 $PrintTV = '';
192 }
193 if(!$this->QuickSearch){
194 $SearchString = '';
195 }
196 if(!$this->AllowCSV){
197 $CSV_x = '';
198 }
199
200 // enforce record selection if user has edit/delete permissions on the current table
201 $AllowPrintDV=1;
202 $this->Permissions=getTablePermissions($this->TableName);
203 if($this->Permissions[3] || $this->Permissions[4]){ // current user can edit or delete?
204 $this->AllowSelection = 1;
205 }elseif(!$this->AllowSelection){
206 $SelectedID='';
207 $AllowPrintDV=0;
208 $PrintDV='';
209 }
210
211 if(!$this->AllowSelection || !$SelectedID){ $dvprint_x=''; }
212
213 $this->QueryFieldsIndexed=reIndex($this->QueryFieldsFilters);
214
215 // determine type of current view: TV, DV, TVDV, TVP, DVP or Filters?
216 if($this->SeparateDV){
217 $current_view = 'TV';
218 if($Print_x != '' || $PrintTV != '') $current_view = 'TVP';
219 elseif($dvprint_x != '' || $PrintDV != '') $current_view = 'DVP';
220 elseif($Filter_x != '') $current_view = 'Filters';
221 elseif(($SelectedID && !$deselect_x && !$delete_x) || $addNew_x != '') $current_view = 'DV';
222 }else{
223 $current_view = 'TVDV';
224 if($Print_x != '' || $PrintTV != '') $current_view = 'TVP';
225 elseif($dvprint_x != '' || $PrintDV != '') $current_view = 'DVP';
226 elseif($Filter_x != '') $current_view = 'Filters';
227 }
228
229 $this->HTML .= '<div class="row"><div class="col-xs-12">';
230 $this->HTML .= '<form ' . (datalist_image_uploads_exist ? 'enctype="multipart/form-data" ' : '') . 'method="post" name="myform" action="' . $this->ScriptFileName . '">';
231 if($Embedded) $this->HTML .= '<input name="Embedded" value="1" type="hidden">';
232 if($AutoClose) $this->HTML .= '<input name="AutoClose" value="1" type="hidden">';
233 $this->HTML .= '<script>';
234 $this->HTML .= 'function enterAction(){';
235 $this->HTML .= ' if($j("input[name=SearchString]:focus").length){ $j("#Search").click(); }';
236 $this->HTML .= ' return false;';
237 $this->HTML .= '}';
238 $this->HTML .= '</script>';
239 $this->HTML .= '<input id="EnterAction" type="submit" style="position: absolute; left: 0px; top: -250px;" onclick="return enterAction();">';
240
241 $this->ContentType='tableview'; // default content type
242
243 if($PrintTV != ''){
244 $Print_x = 1;
245 $_REQUEST['Print_x'] = 1;
246 }
247
248 // handle user commands ...
249 if($deselect_x != ''){
250 $SelectedID = '';
251 $this->showTV();
252 }
253
254 elseif($insert_x != ''){
255 $SelectedID = call_user_func($this->TableName.'_insert');
256
257 // redirect to a safe url to avoid refreshing and thus
258 // insertion of duplicate records.
259 $url = $this->RedirectAfterInsert;
260 $insert_status = 'record-added-ok=' . rand();
261 if(!$SelectedID) $insert_status = 'record-added-error=' . rand();
262
263 // compose filters and sorting
264 foreach($this->filterers as $filterer => $caption){
265 if($_REQUEST['filterer_' . $filterer] != '') $filtersGET .= '&filterer_' . $filterer . '=' . urlencode($_REQUEST['filterer_' . $filterer]);
266 }
267 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
268 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
269 $filtersGET .= "&FilterAnd[{$i}]={$FilterAnd[$i]}&FilterField[{$i}]={$FilterField[$i]}&FilterOperator[{$i}]={$FilterOperator[$i]}&FilterValue[{$i}]=" . urlencode($FilterValue[$i]);
270 }
271 }
272 if($Embedded) $filtersGET .= '&Embedded=1&SelectedID=' . urlencode($SelectedID);
273 if($AutoClose) $filtersGET .= '&AutoClose=1';
274 $filtersGET .= "&SortField={$SortField}&SortDirection={$SortDirection}&FirstRecord={$FirstRecord}";
275 $filtersGET .= "&DisplayRecords={$DisplayRecords}";
276 $filtersGET .= '&SearchString=' . urlencode($SearchString);
277 $filtersGET = substr($filtersGET, 1); // remove initial &
278
279 if($url){
280 /* if designer specified a redirect-after-insert url */
281 $url .= (strpos($url, '?') !== false ? '&' : '?') . $insert_status;
282 $url .= (strpos($url, $this->ScriptFileName) !== false ? "&{$filtersGET}" : '');
283 $url = str_replace("#ID#", urlencode($SelectedID), $url);
284 }else{
285 /* if no redirect-after-insert url, use default */
286 $url = "{$this->ScriptFileName}?{$insert_status}&{$filtersGET}";
287
288 /* if DV and TV in same page, select new record */
289 if(!$this->SeparateDV) $url .= '&SelectedID=' . urlencode($SelectedID);
290 }
291
292 @header('Location: ' . $url);
293 $this->HTML .= "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;url=" . $url ."\">";
294
295 return;
296 }
297
298 elseif($delete_x != ''){
299 $delete_res = call_user_func($this->TableName.'_delete', $SelectedID, $this->AllowDeleteOfParents, $SkipChecks);
300 // handle ajax delete requests
301 if(is_ajax()){
302 die($delete_res ? $delete_res : 'OK');
303 }
304
305 if($delete_res){
306 //$_REQUEST['record-deleted-error'] = 1;
307 $this->HTML .= showNotifications($delete_res, 'alert alert-danger', false);
308 $this->hideTV();
309 $current_view = ($this->SeparateDV ? 'DV' : 'TVDV');
310 }else{
311 $_REQUEST['record-deleted-ok'] = 1;
312 $SelectedID = '';
313 $this->showTV();
314
315 /* close window if embedded */
316 if($Embedded){
317 $this->HTML .= '<script>$j(function(){ setTimeout(function(){ AppGini.closeParentModal(); }, 2000); })</script>';
318 }
319 }
320 }
321
322 elseif($update_x != ''){
323 $updated = call_user_func($this->TableName.'_update', $SelectedID);
324
325 $update_status = 'record-updated-ok=' . rand();
326 if($updated === false) $update_status = 'record-updated-error=' . rand();
327
328 // compose filters and sorting
329 foreach($this->filterers as $filterer => $caption){
330 if($_REQUEST['filterer_' . $filterer] != '') $filtersGET .= '&filterer_' . $filterer . '=' . urlencode($_REQUEST['filterer_' . $filterer]);
331 }
332 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
333 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
334 $filtersGET .= "&FilterAnd[{$i}]={$FilterAnd[$i]}&FilterField[{$i}]={$FilterField[$i]}&FilterOperator[{$i}]={$FilterOperator[$i]}&FilterValue[{$i}]=" . urlencode($FilterValue[$i]);
335 }
336 }
337 $filtersGET .= "&SortField={$SortField}&SortDirection={$SortDirection}&FirstRecord={$FirstRecord}&Embedded={$Embedded}";
338 if($AutoClose) $filtersGET .= '&AutoClose=1';
339 $filtersGET .= "&DisplayRecords={$DisplayRecords}";
340 $filtersGET .= '&SearchString=' . urlencode($SearchString);
341 $filtersGET = substr($filtersGET, 1); // remove initial &
342
343 $redirectUrl = $this->ScriptFileName . '?SelectedID=' . urlencode($SelectedID) . '&' . $filtersGET . '&' . $update_status;
344 @header("Location: $redirectUrl");
345 $this->HTML .= '<META HTTP-EQUIV="Refresh" CONTENT="0;url='.$redirectUrl.'">';
346 return;
347 }
348
349 elseif($addNew_x != ''){
350 $SelectedID='';
351 $this->hideTV();
352 }
353
354 elseif($Print_x != ''){
355 // print code here ....
356 $this->AllowNavigation = 0;
357 $this->AllowSelection = 0;
358 }
359
360 elseif($SaveFilter_x != '' && $this->AllowSavingFilters){
361 $filter_link = $_SERVER['HTTP_REFERER'] . '?SortField=' . urlencode($SortField) . '&SortDirection=' . $SortDirection . '&';
362 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
363 if(($FilterField[$i] != '' || $i == 1) && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
364 $filter_link .= urlencode("FilterAnd[$i]") . '=' . urlencode($FilterAnd[$i]) . '&';
365 $filter_link .= urlencode("FilterField[$i]") . '=' . urlencode($FilterField[$i]) . '&';
366 $filter_link .= urlencode("FilterOperator[$i]") . '=' . urlencode($FilterOperator[$i]) . '&';
367 $filter_link .= urlencode("FilterValue[$i]") . '=' . urlencode($FilterValue[$i]) . '&';
368 }elseif(($i % $FiltersPerGroup == 1) && in_array($FilterAnd[$i], array('and', 'or'))){
369 /* always include the and/or at the beginning of each group */
370 $filter_link .= urlencode("FilterAnd[$i]") . '=' . urlencode($FilterAnd[$i]) . '&';
371 }
372 }
373 $filter_link = substr($filter_link, 0, -1); /* trim last '&' */
374
375 $this->HTML .= '<div id="saved_filter_source_code" class="row"><div class="col-md-6 col-md-offset-3">';
376 $this->HTML .= '<div class="panel panel-info">';
377 $this->HTML .= '<div class="panel-heading"><h3 class="panel-title">' . $Translation["saved filters title"] . "</h3></div>";
378 $this->HTML .= '<div class="panel-body">';
379 $this->HTML .= $Translation["saved filters instructions"];
380 $this->HTML .= '<textarea rows="4" class="form-control vspacer-lg" style="width: 100%;" onfocus="$j(this).select();">' . "<a href=\"{$filter_link}\">Saved filter link<a>" . '</textarea>';
381 $this->HTML .= "<div><a href=\"{$filter_link}\" title=\"" . html_attr($filter_link) . "\">{$Translation['permalink']}</a></div>";
382 $this->HTML .= '<button type="button" class="btn btn-default btn-block vspacer-lg" onclick="$j(\'#saved_filter_source_code\').remove();"><i class="glyphicon glyphicon-remove"></i> ' . $Translation['hide code'] . '</button>';
383 $this->HTML .= '</div>';
384 $this->HTML .= '</div>';
385 $this->HTML .= '</div></div>';
386 }
387
388 elseif($Filter_x != ''){
389 $orderBy = array();
390 if($SortField){
391 $sortFields = explode(',', $SortField);
392 $i=0;
393 foreach($sortFields as $sf){
394 $tob = preg_split('/\s+/', $sf, 2);
395 $orderBy[] = array(trim($tob[0]) => (strtolower(trim($tob[1]))=='desc' ? 'desc' : 'asc'));
396 $i++;
397 }
398 $orderBy[$i-1][$tob[0]] = (strtolower(trim($SortDirection))=='desc' ? 'desc' : 'asc');
399 }
400
401 $currDir=dirname(__FILE__).'/hooks'; // path to hooks folder
402 $uff="{$currDir}/{$this->TableName}.filters.{$mi['username']}.php"; // user-specific filter file
403 $gff="{$currDir}/{$this->TableName}.filters.{$mi['group']}.php"; // group-specific filter file
404 $tff="{$currDir}/{$this->TableName}.filters.php"; // table-specific filter file
405
406 /*
407 if no explicit filter file exists, look for filter files in the hooks folder in this order:
408 1. tablename.filters.username.php ($uff)
409 2. tablename.filters.groupname.php ($gff)
410 3. tablename.filters.php ($tff)
411 */
412 if(!is_file($this->FilterPage)){
413 $this->FilterPage='defaultFilters.php';
414 if(is_file($uff)){
415 $this->FilterPage=$uff;
416 }elseif(is_file($gff)){
417 $this->FilterPage=$gff;
418 }elseif(is_file($tff)){
419 $this->FilterPage=$tff;
420 }
421 }
422
423 if($this->FilterPage!=''){
424 ob_start();
425 @include($this->FilterPage);
426 $out=ob_get_contents();
427 ob_end_clean();
428 $this->HTML .= $out;
429 }
430 // hidden variables ....
431 $this->HTML .= '<input name="SortField" value="'.$SortField.'" type="hidden" />';
432 $this->HTML .= '<input name="SortDirection" type="hidden" value="'.$SortDirection.'" />';
433 $this->HTML .= '<input name="FirstRecord" type="hidden" value="1" />';
434
435 $this->ContentType='filters';
436 $this->set_headers();
437 return;
438 }
439
440 elseif($NoFilter_x != ''){
441 // clear all filters ...
442 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
443 $FilterField[$i] = '';
444 $FilterOperator[$i] = '';
445 $FilterValue[$i] = '';
446 }
447 $DisplayRecords = 'all';
448 $SearchString = '';
449 $FirstRecord = 1;
450
451 // clear filterers
452 foreach($this->filterers as $filterer => $caption){
453 $_REQUEST['filterer_' . $filterer] = '';
454 }
455 }
456
457 elseif($SelectedID){
458 $this->hideTV();
459 }
460
461 // apply lookup filterers to the query
462 foreach($this->filterers as $filterer => $caption){
463 if($_REQUEST['filterer_' . $filterer] != ''){
464 if($this->QueryWhere == '')
465 $this->QueryWhere = "where ";
466 else
467 $this->QueryWhere .= " and ";
468 $this->QueryWhere .= "`{$this->TableName}`.`$filterer`='" . makeSafe($_REQUEST['filterer_' . $filterer]) . "' ";
469 break; // currently, only one filterer can be applied at a time
470 }
471 }
472
473 // apply quick search to the query
474 if($SearchString != ''){
475 if($Search_x!=''){ $FirstRecord=1; }
476
477 if($this->QueryWhere=='')
478 $this->QueryWhere = "where ";
479 else
480 $this->QueryWhere .= " and ";
481
482 foreach($this->QueryFieldsQS as $fName => $fCaption){
483 if(strpos($fName, '<img')===False){
484 $this->QuerySearchableFields[$fName]=$fCaption;
485 }
486 }
487
488 $this->QueryWhere.='('.implode(" LIKE '%".makeSafe($SearchString)."%' or ", array_keys($this->QuerySearchableFields))." LIKE '%".makeSafe($SearchString)."%')";
489 }
490
491
492 // set query filters
493 $QueryHasWhere = 0;
494 if(strpos($this->QueryWhere, 'where ')!==FALSE)
495 $QueryHasWhere = 1;
496
497 $WhereNeedsClosing = 0;
498 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i+=$FiltersPerGroup){ // Number of filters allowed
499 // test current filter group
500 $GroupHasFilters = 0;
501 for($j = 0; $j < $FiltersPerGroup; $j++){
502 if($FilterField[$i+$j] != '' && $this->QueryFieldsIndexed[($FilterField[$i+$j])] != '' && $FilterOperator[$i+$j] != '' && ($FilterValue[$i+$j] != '' || strpos($FilterOperator[$i+$j], 'empty'))){
503 $GroupHasFilters = 1;
504 break;
505 }
506 }
507
508 if($GroupHasFilters){
509 if(!stristr($this->QueryWhere, "where "))
510 $this->QueryWhere = "where (";
511 elseif($QueryHasWhere){
512 $this->QueryWhere .= " and (";
513 $QueryHasWhere = 0;
514 }
515
516 $this->QueryWhere .= " <FilterGroup> " . $FilterAnd[$i] . " (";
517
518 for($j = 0; $j < $FiltersPerGroup; $j++){
519 if($FilterField[$i+$j] != '' && $this->QueryFieldsIndexed[($FilterField[$i+$j])] != '' && $FilterOperator[$i+$j] != '' && ($FilterValue[$i+$j] != '' || strpos($FilterOperator[$i+$j], 'empty'))){
520 if($FilterAnd[$i+$j]==''){
521 $FilterAnd[$i+$j]='and';
522 }
523 // test for date/time fields
524 $tries = 0; $isDateTime = $isDate = false;
525 $fieldName=str_replace('`', '', $this->QueryFieldsIndexed[($FilterField[$i+$j])]);
526 list($tn, $fn)=explode('.', $fieldName);
527 while(!($res = sql("show columns from `{$tn}` like '{$fn}'", $eo)) && $tries < 2){
528 $tn=substr($tn, 0, -1);
529 $tries++;
530 }
531 if($row = @db_fetch_array($res)){
532 $isDateTime = in_array($row['Type'], array('date', 'time', 'datetime'));
533 $isDate = in_array($row['Type'], array('date', 'datetime'));
534 }
535 // end of test
536 if($FilterOperator[$i+$j]=='is-empty' && !$isDateTime){
537 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " (" . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "='' or " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " is NULL) </FilterItem>";
538 }elseif($FilterOperator[$i+$j]=='is-not-empty' && !$isDateTime){
539 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "!='' </FilterItem>";
540 }elseif($FilterOperator[$i+$j]=='is-empty' && $isDateTime){
541 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " (" . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "=0 or " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " is NULL) </FilterItem>";
542 }elseif($FilterOperator[$i+$j]=='is-not-empty' && $isDateTime){
543 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "!=0 </FilterItem>";
544 }elseif($FilterOperator[$i+$j]=='like' && !strstr($FilterValue[$i+$j], "%") && !strstr($FilterValue[$i+$j], "_")){
545 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " like '%" . makeSafe($FilterValue[$i+$j]) . "%' </FilterItem>";
546 }elseif($FilterOperator[$i+$j]=='not-like' && !strstr($FilterValue[$i+$j], "%") && !strstr($FilterValue[$i+$j], "_")){
547 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " not like '%" . makeSafe($FilterValue[$i+$j]) . "%' </FilterItem>";
548 }elseif($isDate){
549 $dateValue = mysql_datetime($FilterValue[$i + $j]);
550 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " " . $GLOBALS['filter_operators'][$FilterOperator[$i+$j]] . " '$dateValue' </FilterItem>";
551 }else{
552 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " " . $GLOBALS['filter_operators'][$FilterOperator[$i+$j]] . " '" . makeSafe($FilterValue[$i+$j]) . "' </FilterItem>";
553 }
554 }
555 }
556
557 $this->QueryWhere .= ") </FilterGroup>";
558 $WhereNeedsClosing = 1;
559 }
560 }
561
562 if($WhereNeedsClosing)
563 $this->QueryWhere .= ")";
564
565 // set query sort
566 if(!stristr($this->QueryOrder, "order by ") && $SortField != '' && $this->AllowSorting){
567 $actualSortField = $SortField;
568 foreach($this->SortFields as $fieldNum => $fieldSort){
569 $actualSortField = str_replace(" $fieldNum ", " $fieldSort ", " $actualSortField ");
570 $actualSortField = str_replace(",$fieldNum ", ",$fieldSort ", " $actualSortField ");
571 }
572 $this->QueryOrder = "order by $actualSortField $SortDirection";
573 }
574
575 // clean up query
576 $this->QueryWhere = str_replace('( <FilterGroup> and ', '( ', $this->QueryWhere);
577 $this->QueryWhere = str_replace('( <FilterGroup> or ', '( ', $this->QueryWhere);
578 $this->QueryWhere = str_replace('( <FilterItem> and ', '( ', $this->QueryWhere);
579 $this->QueryWhere = str_replace('( <FilterItem> or ', '( ', $this->QueryWhere);
580 $this->QueryWhere = str_replace('<FilterGroup>', '', $this->QueryWhere);
581 $this->QueryWhere = str_replace('</FilterGroup>', '', $this->QueryWhere);
582 $this->QueryWhere = str_replace('<FilterItem>', '', $this->QueryWhere);
583 $this->QueryWhere = str_replace('</FilterItem>', '', $this->QueryWhere);
584
585 // if no 'order by' clause found, apply default sorting if specified
586 if($this->DefaultSortField != '' && $this->QueryOrder == ''){
587 $this->QueryOrder="order by ".$this->DefaultSortField." ".$this->DefaultSortDirection;
588 }
589
590 // get count of matching records ...
591 $TempQuery = 'SELECT count(1) from '.$this->QueryFrom.' '.$this->QueryWhere;
592 $RecordCount = sqlValue($TempQuery);
593 $FieldCountTV = count($this->QueryFieldsTV);
594 $FieldCountCSV = count($this->QueryFieldsCSV);
595 $FieldCountFilters = count($this->QueryFieldsFilters);
596 if(!$RecordCount){
597 $FirstRecord=1;
598 }
599
600 // Output CSV on request
601 if($CSV_x != ''){
602 $this->HTML = '';
603 if(datalist_db_encoding == 'UTF-8') $this->HTML = "\xEF\xBB\xBF"; // BOM characters for UTF-8 output
604
605 // execute query for CSV output
606 $fieldList='';
607 foreach($this->QueryFieldsCSV as $fn=>$fc)
608 $fieldList.="$fn as `$fc`, ";
609 $fieldList=substr($fieldList, 0, -2);
610 $csvQuery = 'SELECT '.$fieldList.' from '.$this->QueryFrom.' '.$this->QueryWhere.' '.$this->QueryOrder;
611
612 // hook: table_csv
613 if(function_exists($this->TableName.'_csv')){
614 $args = array();
615 $mq = call_user_func_array($this->TableName . '_csv', array($csvQuery, $mi, &$args));
616 $csvQuery = ($mq ? $mq : $csvQuery);
617 }
618
619 $result = sql($csvQuery, $eo);
620
621 // output CSV field names
622 for($i = 0; $i < $FieldCountCSV; $i++)
623 $this->HTML .= "\"" . db_field_name($result, $i) . "\"" . $this->CSVSeparator;
624 $this->HTML .= "\n\n";
625
626 // output CSV data
627 while($row = db_fetch_row($result)){
628 for($i = 0; $i < $FieldCountCSV; $i++)
629 $this->HTML .= "\"" . str_replace(array("\r\n", "\r", "\n", '"'), array(' ', ' ', ' ', '""'), strip_tags($row[$i])) . "\"" . $this->CSVSeparator;
630 $this->HTML .= "\n\n";
631 }
632 $this->HTML = str_replace($this->CSVSeparator . "\n\n", "\n", $this->HTML);
633 $this->HTML = substr($this->HTML, 0, - 1);
634
635 // clean any output buffers
636 while(@ob_end_clean());
637
638 // output CSV HTTP headers ...
639 header('HTTP/1.1 200 OK');
640 header('Date: ' . @date("D M j G:i:s T Y"));
641 header('Last-Modified: ' . @date("D M j G:i:s T Y"));
642 header("Content-Type: application/force-download");
643 header("Content-Length: " . (string)(strlen($this->HTML)));
644 header("Content-Transfer-Encoding: Binary");
645 header("Content-Disposition: attachment; filename=$this->TableName.csv");
646
647 // send output and quit script
648 echo $this->HTML;
649 exit;
650 }
651 $t = time(); // just a random number for any purpose ...
652
653 // should SelectedID be reset on clicking TV buttons?
654 $resetSelection = ($this->SeparateDV ? "document.myform.SelectedID.value = '';" : "document.myform.writeAttribute('novalidate', 'novalidate');");
655
656 if($current_view == 'DV' && !$Embedded){
657 $this->HTML .= '<div class="page-header">';
658 $this->HTML .= '<h1>';
659 $this->HTML .= '<a style="text-decoration: none; color: inherit;" href="' . $this->TableName . '_view.php"><img src="' . $this->TableIcon . '"> ' . $this->TableTitle . '</a>';
660 /* show add new button if user can insert and there is a selected record */
661 if($SelectedID && $this->Permissions[1] && $this->SeparateDV && $this->AllowInsert){
662 $this->HTML .= ' <button type="submit" id="addNew" name="addNew_x" value="1" class="btn btn-success"><i class="glyphicon glyphicon-plus-sign"></i> ' . $Translation['Add New'] . '</button>';
663 }
664 $this->HTML .= '</h1>';
665 $this->HTML .= '</div>';
666 }
667
668 // quick search and TV action buttons
669 if(!$this->HideTableView && !($dvprint_x && $this->AllowSelection && $SelectedID) && !$PrintDV){
670 $buttons_all = $quick_search_html = '';
671
672 if($Print_x == ''){
673
674 // display 'Add New' icon
675 if($this->Permissions[1] && $this->SeparateDV && $this->AllowInsert){
676 $buttons_all .= '<button type="submit" id="addNew" name="addNew_x" value="1" class="btn btn-success"><i class="glyphicon glyphicon-plus-sign"></i> ' . $Translation['Add New'] . '</button>';
677 $buttonsCount++;
678 }
679
680 // display Print icon
681 if($this->AllowPrinting){
682 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="Print_x" id="Print" value="1" class="btn btn-default"><i class="glyphicon glyphicon-print"></i> ' . $Translation['Print Preview'] . '</button>';
683 $buttonsCount++;
684 }
685
686 // display CSV icon
687 if($this->AllowCSV){
688 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="CSV_x" id="CSV" value="1" class="btn btn-default"><i class="glyphicon glyphicon-download-alt"></i> ' . $Translation['CSV'] . '</button>';
689 $buttonsCount++;
690 }
691
692 // display Filter icon
693 if($this->AllowFilters){
694 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="Filter_x" id="Filter" value="1" class="btn btn-default"><i class="glyphicon glyphicon-filter"></i> ' . $Translation['filter'] . '</button>';
695 $buttonsCount++;
696 }
697
698 // display Show All icon
699 if(($this->AllowFilters)){
700 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="NoFilter_x" id="NoFilter" value="1" class="btn btn-default"><i class="glyphicon glyphicon-remove-circle"></i> ' . $Translation['Reset Filters'] . '</button>';
701 $buttonsCount++;
702 }
703
704 $quick_search_html .= quick_search_html($SearchString, $this->QuickSearchText, $this->SeparateDV);
705 }else{
706 $buttons_all .= '<button class="btn btn-primary" type="button" id="sendToPrinter" onClick="window.print();"><i class="glyphicon glyphicon-print"></i> ' . $Translation['Print'] . '</button>';
707 $buttons_all .= '<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-remove-circle"></i> ' . $Translation['Cancel Printing'] . '</button>';
708 }
709
710 /* if user can print DV, add action to 'More' menu */
711 $selected_records_more = array();
712
713 if($AllowPrintDV){
714 $selected_records_more[] = array(
715 'function' => ($this->SeparateDV ? 'print_multiple_dv_sdv' : 'print_multiple_dv_tvdv'),
716 'title' => $Translation['Print Preview Detail View'],
717 'icon' => 'print'
718 );
719 }
720
721 /* if user can mass-delete selected records, add action to 'More' menu */
722 if($this->AllowMassDelete && $this->AllowDelete){
723 $selected_records_more[] = array(
724 'function' => 'mass_delete',
725 'title' => $Translation['Delete'],
726 'icon' => 'trash',
727 'class' => 'text-danger'
728 );
729 }
730
731 /* if user is admin, add 'Change owner' action to 'More' menu */
732 /* also, add help link for adding more actions */
733 if($mi['admin']){
734 $selected_records_more[] = array(
735 'function' => 'mass_change_owner',
736 'title' => $Translation['Change owner'],
737 'icon' => 'user'
738 );
739 $selected_records_more[] = array(
740 'function' => 'add_more_actions_link',
741 'title' => $Translation['Add more actions'],
742 'icon' => 'question-sign',
743 'class' => 'text-info'
744 );
745 }
746
747 /* user-defined actions ... should be set in the {tablename}_batch_actions() function in hooks/{tablename}.php */
748 $user_actions = array();
749 if(function_exists($this->TableName.'_batch_actions')){
750 $args = array();
751 $user_actions = call_user_func_array($this->TableName . '_batch_actions', array(&$args));
752 if(is_array($user_actions) && count($user_actions)){
753 $selected_records_more = array_merge($selected_records_more, $user_actions);
754 }
755 }
756
757 $actual_more_count = 0;
758 $more_menu = $more_menu_js = '';
759 if(count($selected_records_more)){
760 $more_menu .= '<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" id="selected_records_more"><i class="glyphicon glyphicon-check"></i> ' . $Translation['More'] . ' <span class="caret"></span></button>';
761 $more_menu .= '<ul class="dropdown-menu" role="menu">';
762 foreach($selected_records_more as $action){
763 if(!$action['function'] || !$action['title']) continue;
764 $action['class'] = (!isset($action['class']) ? '' : $action['class']);
765 $action['icon'] = (!isset($action['icon']) ? '' : $action['icon']);
766 $actual_more_count++;
767 $more_menu .= '<li>' .
768 '<a href="#" id="selected_records_' . $action['function'] . '">' .
769 '<span class="' . $action['class'] . '">' .
770 ($action['icon'] ? '<i class="glyphicon glyphicon-' . $action['icon'] . '"></i> ' : '') .
771 $action['title'] .
772 '</span>' .
773 '</a>' .
774 '</li>';
775
776 // on clicking an action, call its js handler function, passing the current table name and an array of selected IDs to it
777 $more_menu_js .= "jQuery('[id=selected_records_{$action['function']}]').click(function(){ {$action['function']}('{$this->TableName}', get_selected_records_ids()); return false; });";
778 }
779 $more_menu .= '</ul>';
780 }
781
782 if($Embedded){
783 $this->HTML .= '<script>$j(function(){ $j(\'[id^=notification-]\').parent().css({\'margin-top\': \'15px\', \'margin-bottom\': \'0\'}); })</script>';
784 }else{
785 $this->HTML .= '<div class="page-header">';
786 $this->HTML .= '<h1>';
787 $this->HTML .= '<div class="row">';
788 $this->HTML .= '<div class="col-sm-8">';
789 $this->HTML .= '<a style="text-decoration: none; color: inherit;" href="' . $this->TableName . '_view.php"><img src="' . $this->TableIcon . '"> ' . $this->TableTitle . '</a>';
790 /* show add new button if user can insert and there is a selected record */
791 if($SelectedID && $this->Permissions[1] && !$this->SeparateDV && $this->AllowInsert){
792 $this->HTML .= ' <button type="submit" id="addNew" name="addNew_x" value="1" class="btn btn-success"><i class="glyphicon glyphicon-plus-sign"></i> ' . $Translation['Add New'] . '</button>';
793 }
794 $this->HTML .= '</div>';
795 if($this->QuickSearch){
796 $this->HTML .= '<div class="col-sm-4">';
797 $this->HTML .= $quick_search_html;
798 $this->HTML .= '</div>';
799 }
800 $this->HTML .= '</div>';
801 $this->HTML .= '</h1>';
802 $this->HTML .= '</div>';
803
804 $this->HTML .= '<div id="top_buttons" class="hidden-print">';
805 /* .all_records: container for buttons that don't need a selection */
806 /* .selected_records: container for buttons that need a selection */
807 $this->HTML .= '<div class="btn-group btn-group-lg visible-md visible-lg all_records pull-left">' . $buttons_all . '</div>';
808 $this->HTML .= '<div class="btn-group btn-group-lg visible-md visible-lg selected_records hidden pull-left hspacer-lg">' . ($actual_more_count ? $more_menu : '') . '</div>';
809 $this->HTML .= '<div class="btn-group-vertical btn-group-lg visible-xs visible-sm all_records">' . $buttons_all . '</div>';
810 $this->HTML .= '<div class="btn-group-vertical btn-group-lg visible-xs visible-sm selected_records hidden vspacer-lg">' . ($actual_more_count ? $more_menu : '') . '</div>';
811 $this->HTML .= $this->tv_tools();
812 $this->HTML .= '<p></p>';
813 $this->HTML .= '</div>';
814
815 $this->HTML .= '<div class="row"><div class="table_view col-xs-12 ' . $this->TVClasses . '">';
816 }
817
818 if($Print_x != ''){
819 /* fix top margin for print-preview */
820 $this->HTML .= '<style>body{ padding-top: 0 !important; }</style>';
821
822 /* disable links inside table body to prevent printing their href */
823 $this->HTML .= '<script>jQuery(function(){ jQuery("tbody a").removeAttr("href").removeAttr("rel"); });</script>';
824 }
825
826 // script for focusing into the search box on loading the page
827 // and for declaring record action handlers
828 $this->HTML .= '<script>jQuery(function(){ jQuery("input[name=SearchString]").focus(); ' . $more_menu_js . ' });</script>';
829
830 }
831
832 // begin table and display table title
833 if(!$this->HideTableView && !($dvprint_x && $this->AllowSelection && $SelectedID) && !$PrintDV && !$Embedded){
834 $this->HTML .= '<div class="table-responsive"><table class="table table-striped table-bordered table-hover">';
835
836 $this->HTML .= '<thead><tr>';
837 if(!$Print_x) $this->HTML .= '<th style="width: 18px;" class="text-center"><input class="hidden-print" type="checkbox" title="' . html_attr($Translation['Select all records']) . '" id="select_all_records"></th>';
838 // Templates
839 $rowTemplate = $selrowTemplate = '';
840 if($this->Template){
841 $rowTemplate = @file_get_contents('./' . $this->Template);
842 if($rowTemplate && $this->SelectedTemplate){
843 $selrowTemplate = @file_get_contents('./' . $this->SelectedTemplate);
844 }
845 }
846
847 // process translations
848 if($rowTemplate){
849 foreach($Translation as $symbol=>$trans){
850 $rowTemplate=str_replace("<%%TRANSLATION($symbol)%%>", $trans, $rowTemplate);
851 }
852 }
853 if($selrowTemplate){
854 foreach($Translation as $symbol=>$trans){
855 $selrowTemplate=str_replace("<%%TRANSLATION($symbol)%%>", $trans, $selrowTemplate);
856 }
857 }
858 // End of templates
859
860 // $this->ccffv: map $FilterField values to field captions as stored in ColCaption
861 $this->ccffv = array();
862 foreach($this->ColCaption as $captionIndex => $caption){
863 $ffv = 1;
864 foreach($this->QueryFieldsFilters as $uselessKey => $filterCaption){
865 if($caption == $filterCaption){
866 $this->ccffv[$captionIndex] = $ffv;
867 }
868 $ffv++;
869 }
870 }
871
872 // display table headers
873 $forceHeaderWidth = false;
874 if($rowTemplate=='' || $this->ShowTableHeader){
875 for($i = 0; $i < count($this->ColCaption); $i++){
876 /* Sorting icon and link */
877 $sort1 = $sort2 = $filterHint = '';
878 if($this->AllowSorting == 1){
879 if($current_view != 'TVP'){
880 $sort1 = "<a href=\"{$this->ScriptFileName}?SortDirection=asc&SortField=".($this->ColNumber[$i])."\" onClick=\"$resetSelection document.myform.NoDV.value=1; document.myform.SortDirection.value='asc'; document.myform.SortField.value = '".($this->ColNumber[$i])."'; document.myform.submit(); return false;\" class=\"TableHeader\">";
881 $sort2 = "</a>";
882 }
883 if($this->ColNumber[$i] == $SortField){
884 $SortDirection = ($SortDirection == "asc" ? "desc" : "asc");
885 if($current_view != 'TVP')
886 $sort1 = "<a href=\"{$this->ScriptFileName}?SortDirection=$SortDirection&SortField=".($this->ColNumber[$i])."\" onClick=\"$resetSelection document.myform.NoDV.value=1; document.myform.SortDirection.value='$SortDirection'; document.myform.SortField.value = ".($this->ColNumber[$i])."; document.myform.submit(); return false;\" class=\"TableHeader\">";
887 $sort2 = " <i class=\"text-warning glyphicon glyphicon-sort-by-attributes" . ($SortDirection == 'desc' ? '' : '-alt') . "\"></i>{$sort2}";
888 $SortDirection = ($SortDirection == "asc" ? "desc" : "asc");
889 }
890 }
891
892 /* Filtering icon and hint */
893 if($this->AllowFilters && is_array($FilterField)){
894 // check to see if there is any filter applied on the current field
895 if(isset($this->ccffv[$i]) && in_array($this->ccffv[$i], $FilterField)){
896 // render filter icon
897 $filterHint = ' <button type="submit" class="btn btn-default btn-xs' . ($current_view == 'TVP' ? ' disabled' : '') . '" name="Filter_x" value="1" title="'.html_attr($Translation['filtered field']).'"><i class="glyphicon glyphicon-filter"></i></button>';
898 }
899 }
900
901 $this->HTML .= "\t<th class=\"{$this->TableName}-{$this->ColFieldName[$i]}\" " . ($forceHeaderWidth ? ' style="width: ' . ($this->ColWidth[$i] ? $this->ColWidth[$i] : 100) . 'px;"' : '') . ">{$sort1}{$this->ColCaption[$i]}{$sort2}{$filterHint}</th>\n";
902 }
903 }elseif($current_view != 'TVP'){
904 // Display a Sort by drop down
905 $this->HTML .= "\t<th class=\"hidden-print\" colspan=\"" . (count($this->ColCaption)) . "\">";
906 $this->HTML .= "\t<div class=\"pull-right\" id=\"order-by-selector\">";
907
908 if($this->AllowSorting == 1){
909 $sortCombo = new Combo;
910 for($i=0; $i < count($this->ColCaption); $i++){
911 $sortCombo->ListItem[] = $this->ColCaption[$i];
912 $sortCombo->ListData[] = $this->ColNumber[$i];
913 }
914 $sortCombo->SelectName = "FieldsList";
915 $sortCombo->SelectedData = $SortField;
916 $sortCombo->Class = '';
917 $sortCombo->SelectedClass = '';
918 $sortCombo->Render();
919 $sortby_dropdown = $sortCombo->HTML;
920 $sortby_dropdown = str_replace('<select ', "<select onChange=\"document.myform.SortDirection.value='$SortDirection'; document.myform.SortField.value=document.myform.FieldsList.value; document.myform.NoDV.value=1; document.myform.submit();\" ", $sortby_dropdown);
921 if($SortField){
922 $SortDirection = ($SortDirection == 'desc' ? 'asc' : 'desc');
923 $sort_class = ($SortDirection == 'asc' ? 'sort-by-attributes-alt' : 'sort-by-attributes');
924 $sort = "<a href=\"javascript: document.myform.NoDV.value=1; document.myform.SortDirection.value='{$SortDirection}'; document.myform.SortField.value='{$SortField}'; document.myform.submit();\" class=TableHeader><i class=\"text-warning glyphicon glyphicon-{$sort_class}\"></i></a>";
925 $SortDirection = ($SortDirection == 'desc' ? 'asc' : 'desc');
926 }else{
927 $sort = '';
928 }
929
930 $sortby_sep = '<span class="hspacer-md"></span>';
931
932 $this->HTML .= "{$Translation['order by']}{$sortby_sep}{$sortby_dropdown}{$sortby_sep}{$sort}{$sortby_sep}";
933 }
934 $this->HTML .= "</div><style>#s2id_FieldsList{ min-width: 12em; width: unset !important; }</style></th>\n";
935 }
936
937 // table view navigation code ...
938 if($RecordCount && $this->AllowNavigation && $RecordCount>$this->RecordsPerPage){
939 while($FirstRecord > $RecordCount)
940 $FirstRecord -= $this->RecordsPerPage;
941
942 if($FirstRecord == '' || $FirstRecord < 1) $FirstRecord = 1;
943
944 if($Previous_x != ''){
945 $FirstRecord -= $this->RecordsPerPage;
946 if($FirstRecord <= 0)
947 $FirstRecord = 1;
948 }elseif($Next_x != ''){
949 $FirstRecord += $this->RecordsPerPage;
950 if($FirstRecord > $RecordCount)
951 $FirstRecord = $RecordCount - ($RecordCount % $this->RecordsPerPage) + 1;
952 if($FirstRecord > $RecordCount)
953 $FirstRecord = $RecordCount - $this->RecordsPerPage + 1;
954 if($FirstRecord <= 0)
955 $FirstRecord = 1;
956 }
957
958 }elseif($RecordCount){
959 $FirstRecord = 1;
960 $this->RecordsPerPage = 2000; // a limit on max records in print preview to avoid performance drops
961 }
962 // end of table view navigation code
963 $this->HTML .= "\n\t</tr>\n\n</thead>\n\n<tbody><!-- tv data below -->\n";
964
965 $i = 0;
966 if($RecordCount){
967 $i = $FirstRecord;
968 // execute query for table view
969 $query_fields = array();
970 foreach($this->QueryFieldsTV as $fn => $fc)
971 $query_fields[] = "$fn as `$fc`";
972 $fieldList = implode(', ', $query_fields);
973
974 if($this->PrimaryKey)
975 $fieldList .= ", $this->PrimaryKey as '" . str_replace('`', '', $this->PrimaryKey) . "'";
976
977 $tvQuery = "SELECT {$fieldList} from {$this->QueryFrom} {$this->QueryWhere} {$this->QueryOrder}";
978 $result = sql($tvQuery . " limit " . ($i-1) . ",{$this->RecordsPerPage}", $eo);
979 while(($row = db_fetch_array($result)) && ($i < ($FirstRecord + $this->RecordsPerPage))){
980 /* skip displaying the current record if we're in TVP or multiple DVP and the record is not checked */
981 if(($PrintTV || $Print_x) && count($record_selector) && !in_array($row[$FieldCountTV], $record_selector)) continue;
982
983 $attr_id = html_attr($row[$FieldCountTV]); /* pk value suitable for inserting into html tag attributes */
984 $js_id = addslashes($row[$FieldCountTV]); /* pk value suitable for inserting into js strings */
985
986 /* show record selector except in TVP */
987 if($Print_x != ''){ $this->HTML .= '<tr>'; }
988
989 if(!$Print_x){
990 $this->HTML .= ($SelectedID == $row[$FieldCountTV] ? '<tr class="active">' : '<tr>');
991 $checked = (is_array($record_selector) && in_array($row[$FieldCountTV], $record_selector) ? ' checked' : '');
992 $this->HTML .= "<td class=\"text-center\"><input class=\"hidden-print record_selector\" type=\"checkbox\" id=\"record_selector_{$attr_id}\" name=\"record_selector[]\" value=\"{$attr_id}\"{$checked}></td>";
993 }
994
995 /* apply record templates */
996 if($rowTemplate != ''){
997 $rowTemp = $rowTemplate;
998 if($this->AllowSelection == 1 && $SelectedID == $row[$FieldCountTV] && $selrowTemplate != ''){
999 $rowTemp = $selrowTemplate;
1000 }
1001
1002 if($this->AllowSelection == 1 && $SelectedID != $row[$FieldCountTV]){
1003 $rowTemp = str_replace('<%%SELECT%%>',"<a onclick=\"document.myform.SelectedField.value=this.parentNode.cellIndex; document.myform.SelectedID.value='" . addslashes($row[$FieldCountTV]) . "'; document.myform.submit(); return false;\" href=\"{$this->ScriptFileName}?SelectedID=" . html_attr($row[$FieldCountTV]) . "\" style=\"display: block; padding:0px;\">",$rowTemp);
1004 $rowTemp = str_replace('<%%ENDSELECT%%>','</a>',$rowTemp);
1005 }else{
1006 $rowTemp = str_replace('<%%SELECT%%>', '', $rowTemp);
1007 $rowTemp = str_replace('<%%ENDSELECT%%>', '', $rowTemp);
1008 }
1009
1010 for($j = 0; $j < $FieldCountTV; $j++){
1011 $fieldTVCaption = current(array_slice($this->QueryFieldsTV, $j, 1));
1012
1013 $fd = safe_html($row[$j]);
1014
1015 /*
1016 the TV template could contain field placeholders in the format
1017 <%%FIELD_n%%> or <%%VALUE(Field caption)%%> or <%%HTML_ATTR(field caption)%%>
1018 */
1019 $rowTemp = str_replace("<%%FIELD_$j%%>", thisOr($fd, ''), $rowTemp);
1020 $rowTemp = str_replace("<%%VALUE($fieldTVCaption)%%>", thisOr($fd, ''), $rowTemp);
1021 $rowTemp = str_replace("<%%HTML_ATTR($fieldTVCaption)%%>", html_attr($fd), $rowTemp);
1022
1023 if(strpos($rowTemp, "<%%YOUTUBETHUMB($fieldTVCaption)%%>") !== false) $rowTemp = str_replace("<%%YOUTUBETHUMB($fieldTVCaption)%%>", thisOr(get_embed('youtube', $fd, '', '', 'thumbnail_url'), 'blank.gif'), $rowTemp);
1024 if(strpos($rowTemp, "<%%GOOGLEMAPTHUMB($fieldTVCaption)%%>") !== false) $rowTemp = str_replace("<%%GOOGLEMAPTHUMB($fieldTVCaption)%%>", thisOr(get_embed('googlemap', $fd, '', '', 'thumbnail_url'), 'blank.gif'), $rowTemp);
1025 if(thisOr($fd)==' ' && preg_match('/<a href=".*? .*?<\/a>/i', $rowTemp, $m)){
1026 $rowTemp=str_replace($m[0], '', $rowTemp);
1027 }
1028 }
1029
1030 $this->HTML .= $rowTemp;
1031 $rowTemp = '';
1032
1033 }else{
1034 // default view if no template
1035 for($j = 0; $j < $FieldCountTV; $j++){
1036 if($this->AllowSelection == 1){
1037 $sel1 = "<a href=\"{$this->ScriptFileName}?SelectedID=" . html_attr($row[$FieldCountTV]) . "\" onclick=\"document.myform.SelectedID.value='" . addslashes($row[$FieldCountTV]) . "'; document.myform.submit(); return false;\" style=\"padding:0px;\">";
1038 $sel2 = "</a>";
1039 }else{
1040 $sel1 = '';
1041 $sel2 = '';
1042 }
1043
1044 $this->HTML .= "<td valign=\"top\"><div> {$sel1}{$row[$j]}{$sel2} </div></td>";
1045 }
1046 }
1047 $this->HTML .= "</tr>\n";
1048 $i++;
1049 }
1050 $i--;
1051 }
1052
1053 $this->HTML = preg_replace("/<a href=\"(mailto:)? [^\n]*title=\" \"><\/a>/", ' ', $this->HTML);
1054 $this->HTML = preg_replace("/<a [^>]*>( )*<\/a>/", ' ', $this->HTML);
1055 $this->HTML = preg_replace("/<%%.*%%>/U", ' ', $this->HTML);
1056 // end of data
1057 $this->HTML.='<!-- tv data above -->';
1058 $this->HTML .= "\n</tbody>";
1059
1060 if($Print_x == ''){ // TV
1061 $pagesMenu = '';
1062 if($RecordCount > $this->RecordsPerPage){
1063 $pagesMenuId = "{$this->TableName}_pagesMenu";
1064 $pagesMenu = $Translation['go to page'] . ' <select style="width: 90%; max-width: 8em;" class="input-sm ltr" id="' . $pagesMenuId . '" onChange="document.myform.writeAttribute(\'novalidate\', \'novalidate\'); document.myform.NoDV.value=1; document.myform.FirstRecord.value=(this.value * ' . $this->RecordsPerPage . '+1); document.myform.submit();">';
1065 $pagesMenu .= '</select>';
1066
1067 $pagesMenu .= '<script>';
1068 $pagesMenu .= 'var lastPage = ' . (ceil($RecordCount / $this->RecordsPerPage) - 1) . ';';
1069 $pagesMenu .= 'var currentPage = ' . (($FirstRecord - 1) / $this->RecordsPerPage) . ';';
1070 $pagesMenu .= 'var pagesMenu = document.getElementById("' . $pagesMenuId . '");';
1071 $pagesMenu .= 'var lump = ' . datalist_max_page_lump . ';';
1072
1073 $pagesMenu .= 'if(lastPage <= lump * 3){';
1074 $pagesMenu .= ' addPageNumbers(0, lastPage);';
1075 $pagesMenu .= '}else{';
1076 $pagesMenu .= ' addPageNumbers(0, lump - 1);';
1077 $pagesMenu .= ' if(currentPage < lump) addPageNumbers(lump, currentPage + lump / 2);';
1078 $pagesMenu .= ' if(currentPage >= lump && currentPage < (lastPage - lump)){';
1079 $pagesMenu .= ' addPageNumbers(';
1080 $pagesMenu .= ' Math.max(currentPage - lump / 2, lump),';
1081 $pagesMenu .= ' Math.min(currentPage + lump / 2, lastPage - lump - 1)';
1082 $pagesMenu .= ' );';
1083 $pagesMenu .= ' }';
1084 $pagesMenu .= ' if(currentPage >= (lastPage - lump)) addPageNumbers(currentPage - lump / 2, lastPage - lump - 1);';
1085 $pagesMenu .= ' addPageNumbers(lastPage - lump, lastPage);';
1086 $pagesMenu .= '}';
1087
1088 $pagesMenu .= 'function addPageNumbers(fromPage, toPage){';
1089 $pagesMenu .= ' var ellipsesIndex = 0;';
1090 $pagesMenu .= ' if(fromPage > toPage) return;';
1091 $pagesMenu .= ' if(fromPage > 0){';
1092 $pagesMenu .= ' if(pagesMenu.options[pagesMenu.options.length - 1].text != fromPage){';
1093 $pagesMenu .= ' ellipsesIndex = pagesMenu.options.length;';
1094 $pagesMenu .= ' fromPage--;';
1095 $pagesMenu .= ' }';
1096 $pagesMenu .= ' }';
1097 $pagesMenu .= ' for(i = fromPage; i <= toPage; i++){';
1098 $pagesMenu .= ' var option = document.createElement("option");';
1099 $pagesMenu .= ' option.text = (i + 1);';
1100 $pagesMenu .= ' option.value = i;';
1101 $pagesMenu .= ' if(i == currentPage){ option.selected = "selected"; }';
1102 $pagesMenu .= ' try{';
1103 $pagesMenu .= ' /* for IE earlier than version 8 */';
1104 $pagesMenu .= ' pagesMenu.add(option, pagesMenu.options[null]);';
1105 $pagesMenu .= ' }catch(e){';
1106 $pagesMenu .= ' pagesMenu.add(option, null);';
1107 $pagesMenu .= ' }';
1108 $pagesMenu .= ' }';
1109 $pagesMenu .= ' if(ellipsesIndex > 0){';
1110 $pagesMenu .= ' pagesMenu.options[ellipsesIndex].text = " ... ";';
1111 $pagesMenu .= ' }';
1112 $pagesMenu .= '}';
1113 $pagesMenu .= '</script>';
1114 }
1115
1116 $this->HTML .= "\n\t";
1117
1118 if($i){ // 1 or more records found
1119 $this->HTML .= "<tfoot><tr><td colspan=".(count($this->ColCaption)+1).'>';
1120 $this->HTML .= $Translation['records x to y of z'];
1121 $this->HTML .= '</td></tr></tfoot>';
1122 }
1123
1124 if(!$i){ // no records found
1125 $this->HTML .= "<tfoot><tr><td colspan=".(count($this->ColCaption)+1).'>';
1126 $this->HTML .= '<div class="alert alert-warning">';
1127 $this->HTML .= '<i class="glyphicon glyphicon-warning-sign"></i> ';
1128 $this->HTML .= $Translation['No matches found!'];
1129 $this->HTML .= '</div>';
1130 $this->HTML .= '</td></tr></tfoot>';
1131 }
1132
1133 }else{ // TVP
1134 if($i) $this->HTML .= "\n\t<tfoot><tr><td colspan=".(count($this->ColCaption) + 1). '>' . $Translation['records x to y of z'] . '</td></tr></tfoot>';
1135 if(!$i) $this->HTML .= "\n\t<tfoot><tr><td colspan=".(count($this->ColCaption) + 1). '>' . $Translation['No matches found!'] . '</td></tr></tfoot>';
1136 }
1137
1138 $this->HTML = str_replace("<FirstRecord>", number_format($FirstRecord), $this->HTML);
1139 $this->HTML = str_replace("<LastRecord>", number_format($i), $this->HTML);
1140 $this->HTML = str_replace("<RecordCount>", number_format($RecordCount), $this->HTML);
1141 $tvShown=true;
1142
1143 $this->HTML .= "</table></div>\n";
1144
1145 /* highlight quick search matches */
1146 if($SearchString!='') $this->HTML .= '<script>$j(function(){ $j(".table-responsive td").mark("' . html_attr($SearchString) . '", { className: "text-warning bg-warning", diacritics: false }); })</script>';
1147
1148 if($Print_x == '' && $i){ // TV
1149 $this->HTML .= '<div class="row pagination-section">';
1150 $this->HTML .= '<div class="col-xs-4 col-md-3 col-lg-2 vspacer-lg">';
1151 $this->HTML .= '<button onClick="' . $resetSelection . ' document.myform.NoDV.value = 1; return true;" type="submit" name="Previous_x" id="Previous" value="1" class="btn btn-default btn-block"><i class="glyphicon glyphicon-chevron-left"></i> <span class="hidden-xs">' . $Translation['Previous'] . '</span></button>';
1152 $this->HTML .= '</div>';
1153
1154 $this->HTML .= '<div class="col-xs-4 col-md-4 col-lg-2 col-md-offset-1 col-lg-offset-3 text-center vspacer-lg">';
1155 $this->HTML .= $pagesMenu;
1156 $this->HTML .= '</div>';
1157
1158 $this->HTML .= '<div class="col-xs-4 col-md-3 col-lg-2 col-md-offset-1 col-lg-offset-3 text-right vspacer-lg">';
1159 $this->HTML .= '<button onClick="'.$resetSelection.' document.myform.NoDV.value=1; return true;" type="submit" name="Next_x" id="Next" value="1" class="btn btn-default btn-block"><span class="hidden-xs">' . $Translation['Next'] . '</span> <i class="glyphicon glyphicon-chevron-right"></i></button>';
1160 $this->HTML .= '</div>';
1161 $this->HTML .= '</div>';
1162 }
1163
1164 $this->HTML .= '</div>'; // end of div.table_view
1165 }
1166 /* that marks the end of the TV table */
1167
1168 // hidden variables ....
1169 foreach($this->filterers as $filterer => $caption){
1170 if($_REQUEST['filterer_' . $filterer] != ''){
1171 $this->HTML .= "<input name=\"filterer_{$filterer}\" value=\"" . html_attr($_REQUEST['filterer_' . $filterer]) . "\" type=\"hidden\" />";
1172 break; // currently, only one filterer can be applied at a time
1173 }
1174 }
1175
1176 $this->HTML .= '<!-- possible values for current_view: TV, TVP, DV, DVP, Filters, TVDV -->';
1177 $this->HTML .= '<input name="current_view" id="current_view" value="' . $current_view . '" type="hidden">';
1178 $this->HTML .= '<input name="SortField" value="' . $SortField . '" type="hidden">';
1179 $this->HTML .= '<input name="SelectedID" value="' . html_attr($SelectedID) . '" type="hidden">';
1180 $this->HTML .= '<input name="SelectedField" value="" type="hidden">';
1181 $this->HTML .= '<input name="SortDirection" type="hidden" value="' . $SortDirection . '">';
1182 $this->HTML .= '<input name="FirstRecord" type="hidden" value="' . $FirstRecord . '">';
1183 $this->HTML .= '<input name="NoDV" type="hidden" value="">';
1184 $this->HTML .= '<input name="PrintDV" type="hidden" value="">';
1185 if($this->QuickSearch && !strpos($this->HTML, 'SearchString')) $this->HTML .= '<input name="SearchString" type="hidden" value="' . html_attr($SearchString) . '">';
1186 // hidden variables: filters ...
1187 $FiltersCode = '';
1188 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
1189 if($i%$FiltersPerGroup == 1 && $i != 1 && $FilterAnd[$i] != ''){
1190 $FiltersCode .= "<input name=\"FilterAnd[$i]\" value=\"$FilterAnd[$i]\" type=\"hidden\">\n";
1191 }
1192 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
1193 if(!strstr($FiltersCode, "<input name=\"FilterAnd[{$i}]\" value="))
1194 $FiltersCode .= "<input name=\"FilterAnd[{$i}]\" value=\"{$FilterAnd[$i]}\" type=\"hidden\">\n";
1195 $FiltersCode .= "<input name=\"FilterField[{$i}]\" value=\"{$FilterField[$i]}\" type=\"hidden\">\n";
1196 $FiltersCode .= "<input name=\"FilterOperator[{$i}]\" value=\"{$FilterOperator[$i]}\" type=\"hidden\">\n";
1197 $FiltersCode .= "<input name=\"FilterValue[{$i}]\" value=\"" . html_attr($FilterValue[$i]) . "\" type=\"hidden\">\n";
1198 }
1199 }
1200 $FiltersCode .= "<input name=\"DisplayRecords\" value=\"$DisplayRecords\" type=\"hidden\" />";
1201 $this->HTML .= $FiltersCode;
1202
1203 // display details form ...
1204 if(($this->AllowSelection || $this->AllowInsert || $this->AllowUpdate || $this->AllowDelete) && $Print_x=='' && !$PrintDV){
1205 if(($this->SeparateDV && $this->HideTableView) || !$this->SeparateDV){
1206 $dvCode = call_user_func("{$this->TableName}_form", $SelectedID, $this->AllowUpdate, (($this->HideTableView && $SelectedID) ? 0 : $this->AllowInsert), $this->AllowDelete, $this->SeparateDV, $this->TemplateDV, $this->TemplateDVP);
1207
1208 $this->HTML .= "\n\t<div class=\"col-xs-12 detail_view {$this->DVClasses}\">{$tv_dv_separator}<div class=\"panel panel-default\">{$dvCode}</div></div>";
1209 $this->HTML .= ($this->SeparateDV ? '<input name="SearchString" value="' . html_attr($SearchString) . '" type="hidden">' : '');
1210 if($dvCode){
1211 $this->ContentType = 'detailview';
1212 $dvShown = true;
1213 }
1214 }
1215 }
1216
1217 // display multiple printable detail views
1218 if($PrintDV){
1219 $dvCode = '';
1220 $_REQUEST['dvprint_x'] = 1;
1221
1222 // hidden vars
1223 foreach($this->filterers as $filterer => $caption){
1224 if($_REQUEST['filterer_' . $filterer] != ''){
1225 $this->HTML .= "<input name=\"filterer_{$filterer}\" value=\"" . html_attr($_REQUEST['filterer_' . $filterer]) . "\" type=\"hidden\" />";
1226 break; // currently, only one filterer can be applied at a time
1227 }
1228 }
1229
1230 // count selected records
1231 $selectedRecords = 0;
1232 if(is_array($record_selector)) foreach($record_selector as $id){
1233 $selectedRecords++;
1234 $this->HTML .= '<input type="hidden" name="record_selector[]" value="' . html_attr($id) . '">'."\n";
1235 }
1236
1237 if($selectedRecords && $selectedRecords <= datalist_max_records_dv_print){ // if records selected > {datalist_max_records_dv_print} don't show DV preview to avoid db performance issues.
1238 foreach($record_selector as $id){
1239 $dvCode .= call_user_func($this->TableName . '_form', $id, 0, 0, 0, 1, $this->TemplateDV, $this->TemplateDVP);
1240 }
1241
1242 if($dvCode!=''){
1243 $dvCode = preg_replace('/<input .*?type="?image"?.*?>/', '', $dvCode);
1244 $this->HTML .= $dvCode;
1245 }
1246 }else{
1247 $this->HTML .= error_message($Translation['Maximum records allowed to enable this feature is'] . ' ' . datalist_max_records_dv_print);
1248 $this->HTML .= '<input type="submit" class="print-button" value="'.$Translation['Print Preview Table View'].'">';
1249 }
1250 }
1251
1252 $this->HTML .= "</div></form>";
1253 $this->HTML .= '</div><div class="col-xs-1 md-hidden lg-hidden"></div></div>';
1254
1255 // $this->HTML .= '<font face="garamond">'.html_attr($tvQuery).'</font>'; // uncomment this line for debugging the table view query
1256
1257 if($dvShown && $tvShown) $this->ContentType='tableview+detailview';
1258 if($dvprint_x!='') $this->ContentType='print-detailview';
1259 if($Print_x!='') $this->ContentType='print-tableview';
1260 if($PrintDV!='') $this->ContentType='print-detailview';
1261
1262 // call detail view javascript hook file if found
1263 $dvJSHooksFile=dirname(__FILE__).'/hooks/'.$this->TableName.'-dv.js';
1264 if(is_file($dvJSHooksFile) && ($this->ContentType=='detailview' || $this->ContentType=='tableview+detailview')){
1265 $this->HTML.="\n<script src=\"hooks/{$this->TableName}-dv.js\"></script>\n";
1266 }
1267
1268 $this->set_headers();
1269 return;
1270 }
1271
1272 function validate_filters($req, $FiltersPerGroup = 4, $is_gpc = true){
1273 $fand = (isset($req['FilterAnd']) && is_array($req['FilterAnd']) ? $req['FilterAnd'] : array());
1274 $ffield = (isset($req['FilterField']) && is_array($req['FilterField']) ? $req['FilterField'] : array());
1275 $fop = (isset($req['FilterOperator']) && is_array($req['FilterOperator']) ? $req['FilterOperator'] : array());
1276 $fvalue = (isset($req['FilterValue']) && is_array($req['FilterValue']) ? $req['FilterValue'] : array());
1277
1278 /* make sure FilterAnd is either 'and' or 'or' */
1279 foreach($fand as $i => $f){
1280 if($f && !preg_match('/^(and|or)$/i', trim($f))) $fand[$i] = 'and';
1281 }
1282
1283 /* FilterField must be a positive integer */
1284 foreach($ffield as $ffi => $ffn){
1285 $ffield[$ffi] = max(0, intval($ffn));
1286 }
1287
1288 /* validate FilterOperator */
1289 foreach($fop as $i => $f){
1290 $fop[$i] = trim($f);
1291 if($f && !in_array(trim($f), array_keys($GLOBALS['filter_operators']))){
1292 $fop[$i] = '';
1293 }
1294 }
1295
1296 /* undo magic quotes if gpc */
1297 if($is_gpc){
1298 foreach($fvalue as $fvi => $fv){
1299 $fvalue[$fvi] = (get_magic_quotes_gpc() ? stripslashes($fv) : $fv);
1300 }
1301 }
1302
1303 /* clear fand, ffield and fop for filters having no value or no field */
1304 /* assume equal-to op and 'and' if missing */
1305 for($i = 1; $i <= datalist_filters_count * $FiltersPerGroup; $i++){
1306 if(!isset($fand[$i]) && !isset($ffield[$i]) && !isset($fop[$i]) && !isset($fvalue[$i])) continue;
1307
1308 if(($fvalue[$i] == '' && !in_array($fop[$i], array('is-empty', 'is-not-empty'))) || !$ffield[$i]){
1309 unset($ffield[$i], $fop[$i], $fvalue[$i]);
1310 if($i % $FiltersPerGroup != 1) unset($fand[$i]);
1311 }else{
1312 if(!$fand[$i]) $fand[$i] = 'and';
1313 if(!$fop[$i]) $fop[$i] = 'equal-to';
1314 }
1315 }
1316
1317 /* empty FilterAnd for empty groups or set to 'and' if empty while group not empty */
1318 for($i = 1; $i <= datalist_filters_count * $FiltersPerGroup; $i += $FiltersPerGroup){
1319 $empty_group = true;
1320
1321 for($j = $i; $j < ($i + $FiltersPerGroup); $j++){
1322 if($ffield[$j]) $empty_group = false;
1323 }
1324
1325 if($empty_group){
1326 $fand[$i] = '';
1327 continue;
1328 }
1329
1330 if(!$fand[$i]) $fand[$i] = 'and';
1331 }
1332
1333 return array($fand, $ffield, $fop, $fvalue);
1334 }
1335
1336 /**
1337 * @brief Returns HTML/JS code for displaying TV table options (hide/show columns)
1338 */
1339 function tv_tools(){
1340 global $Translation;
1341
1342 ob_start();
1343 ?>
1344
1345 <?php if($this->ShowTableHeader){ ?>
1346 <div class="pull-right flip btn-group vspacer-md tv-tools">
1347 <button title="<?php echo html_attr($Translation['hide/show columns']); ?>" type="button" class="btn btn-default tv-toggle" data-toggle="collapse" data-target="#toggle-columns-container"><i class="glyphicon glyphicon-align-justify rotate90"></i></button>
1348 </div>
1349 <?php } ?>
1350
1351 <div class="pull-right flip btn-group vspacer-md hspacer-md tv-tools">
1352 <button title="<?php echo html_attr($Translation['previous column']); ?>" type="button" class="btn btn-default tv-scroll" onclick="AppGini.TVScroll().less()"><i class="glyphicon glyphicon-step-backward"></i></button>
1353 <button title="<?php echo html_attr($Translation['next column']); ?>" type="button" class="btn btn-default tv-scroll" onclick="AppGini.TVScroll().more()"><i class="glyphicon glyphicon-step-forward"></i></button>
1354 </div>
1355 <div class="clearfix"></div>
1356
1357 <?php if($this->ShowTableHeader){ ?>
1358 <div class="collapse" id="toggle-columns-container">
1359 <div class="well pull-right flip" style="width: 100%; max-width: 600px;">
1360 <div class="row" id="toggle-columns">
1361 <div class="col-md-12">
1362 <div class="btn-group" style="width: 100%;">
1363 <button type="button" class="btn btn-default" id="show-all-columns" style="width: 33.3%;"><i class="glyphicon glyphicon-check"></i> <?php echo $Translation['Reset Filters']; ?></button>
1364 <button type="button" class="btn btn-default" id="hide-all-columns" style="width: 33.3%;"><i class="glyphicon glyphicon-unchecked"></i> <?php echo $Translation['hide all']; ?></button>
1365 <button type="button" class="btn btn-default" id="toggle-columns-checks" style="width: 33.4%;"><i class="glyphicon glyphicon-random"></i> <?php echo $Translation['toggle']; ?></button>
1366 </div>
1367 </div>
1368 <div class="col-md-12"><button type="button" class="btn btn-default btn-block" id="toggle-columns-collapser" data-toggle="collapse" data-target="#toggle-columns-container"><i class="glyphicon glyphicon-ok"></i> <?php echo $Translation['ok']; ?></button></div>
1369 </div>
1370 </div>
1371 <div class="clearfix"></div>
1372 </div>
1373 <?php } ?>
1374
1375 <script>
1376 $j(function(){
1377 /**
1378 * @brief Saves/retrieves value of column toggle status
1379 *
1380 * @param [in] col_class class of column concerned
1381 * @param [in] val boolean, optional value to save.
1382 * @return column toggle status if no value is passed
1383 */
1384 var col_cookie = function(col_class, val){
1385 if(col_class === undefined) return true;
1386 if(val !== undefined && val !== true && val !== false) val = true;
1387
1388 var cn = 'columns-' + location.pathname.split(/\//).pop().split(/\./).shift(); // cookie name
1389 var op = { expires: 30, path: '' }; // cookie options
1390 var c = Cookies.getJSON(cn) || {};
1391
1392 /* if no cookie, create it and set it to val (or true if no val) */
1393 if(c[col_class] === undefined){
1394 if(val === undefined) val = true;
1395
1396 c[col_class] = val;
1397 Cookies.set(cn, c, op);
1398 return val;
1399 }
1400
1401 /* if cookie found and val provided, set cookie to new val */
1402 if(val !== undefined){
1403 c[col_class] = val;
1404 Cookies.set(cn, c, op);
1405 return val;
1406 }
1407
1408 /* if cookie found and no val, return cookie val */
1409 return c[col_class];
1410 }
1411
1412 /**
1413 * @brief shows/hides column given its class, and saves this into cookies
1414 *
1415 * @param [in] col_class class of column to show/hide
1416 * @param [in] show boolean, optional. Set to false to hide. Default is true (to show).
1417 */
1418 var show_column = function(col_class, show){
1419 if(col_class == undefined) return;
1420 if(show == undefined) show = true;
1421
1422 if(show === false) $j('.' + col_class).hide();
1423 else $j('.' + col_class).show();
1424
1425 AppGini.TVScroll().reset();
1426
1427 col_cookie(col_class, show);
1428 }
1429
1430 /* initiate TVScroll */
1431 AppGini.TVScroll().less();
1432
1433 <?php if($this->ShowTableHeader){ ?>
1434 /* handle toggling columns' checkboxes */
1435 $j('#toggle-columns-container').on('click', 'input[type=checkbox]', function(){
1436 show_column($j(this).data('col'), $j(this).prop('checked'));
1437 });
1438
1439 /* get TV columns and populate the #toggle-columns section */
1440 $j('.table_view th').each(function(){
1441 var th = $j(this);
1442
1443 /* ignore the record selector column */
1444 if(th.find('#select_all_records').length > 0) return;
1445
1446 var col_class = th.attr('class');
1447 var label = $j.trim(th.text());
1448
1449 /* Add a toggler for the column in the #toggle-columns section */
1450 $j(
1451 '<div class="col-md-6"><div class="checkbox"><label>' +
1452 '<input type="checkbox" data-col="' + col_class + '" checked> ' + label +
1453 '</label></div></div>'
1454 ).insertBefore('#toggle-columns-collapser');
1455
1456 /* load saved column status */
1457 var col_status = col_cookie(col_class);
1458 if(col_status === false) $j('#toggle-columns input[type=checkbox]:last').trigger('click');
1459 });
1460
1461 /* handle clicking 'show all [columns]' */
1462 $j('#show-all-columns').click(function(){
1463 $j('#toggle-columns input[type=checkbox]:not(:checked)').trigger('click');
1464 });
1465
1466 /* handle clicking 'hide all [columns]' */
1467 $j('#hide-all-columns').click(function(){
1468 $j('#toggle-columns input[type=checkbox]:checked').trigger('click');
1469 });
1470
1471 /* handle clicking 'toggle [columns]' */
1472 $j('#toggle-columns-checks').click(function(){
1473 $j('#toggle-columns input[type=checkbox]').trigger('click');
1474 });
1475 <?php } ?>
1476 })
1477 </script>
1478 <?php
1479 return ob_get_clean();
1480 }
1481
1482 }
2
3 define('datalist_filters_count', 20);
4 define('datalist_image_uploads_exist', false);
5 define('datalist_max_records_multi_selection', 1000);
6 define('datalist_max_page_lump', 50);
7 define('datalist_max_records_dv_print', 100);
8 define('datalist_auto_complete_size', 1000);
9 define('datalist_date_separator', '/');
10 define('datalist_date_format', 'mdY');
11
12 $curr_dir = dirname(__FILE__);
13 require_once($curr_dir . '/combo.class.php');
14 require_once($curr_dir . '/data_combo.class.php');
15 require_once($curr_dir . '/date_combo.class.php');
16
17 class DataList{
18 // this class generates the data table ...
19
20 var $QueryFieldsTV,
21 $QueryFieldsCSV,
22 $QueryFieldsFilters,
23 $QueryFieldsQS,
24 $QueryFrom,
25 $QueryWhere,
26 $QueryOrder,
27 $filterers,
28
29 $ColWidth, // array of field widths
30 $DataHeight,
31 $TableName,
32
33 $AllowSelection,
34 $AllowDelete,
35 $AllowMassDelete,
36 $AllowDeleteOfParents,
37 $AllowInsert,
38 $AllowUpdate,
39 $SeparateDV,
40 $Permissions,
41 $AllowFilters,
42 $AllowSavingFilters,
43 $AllowSorting,
44 $AllowNavigation,
45 $AllowPrinting,
46 $HideTableView,
47 $AllowCSV,
48 $CSVSeparator,
49
50 $QuickSearch, // 0 to 3
51
52 $RecordsPerPage,
53 $ScriptFileName,
54 $RedirectAfterInsert,
55 $TableTitle,
56 $PrimaryKey,
57 $DefaultSortField,
58 $DefaultSortDirection,
59
60 // Templates variables
61 $Template,
62 $SelectedTemplate,
63 $TemplateDV,
64 $TemplateDVP,
65 $ShowTableHeader, // 1 = show standard table headers
66 $TVClasses,
67 $DVClasses,
68 // End of templates variables
69
70 $ContentType, // set by DataList to 'tableview', 'detailview', 'tableview+detailview', 'print-tableview', 'print-detailview' or 'filters'
71 $HTML; // generated html after calling Render()
72
73 function __construct(){ // PHP 7 compatibility
74 $this->DataList();
75 }
76
77 function DataList(){ // Constructor function
78 $this->DataHeight = 150;
79
80 $this->AllowSelection = 1;
81 $this->AllowDelete = 1;
82 $this->AllowInsert = 1;
83 $this->AllowUpdate = 1;
84 $this->AllowFilters = 1;
85 $this->AllowNavigation = 1;
86 $this->AllowPrinting = 1;
87 $this->HideTableView = 0;
88 $this->QuickSearch = 0;
89 $this->AllowCSV = 0;
90 $this->CSVSeparator = ",";
91 $this->HighlightColor = '#FFF0C2'; // default highlight color
92
93 $this->RecordsPerPage = 10;
94 $this->Template = '';
95 $this->HTML = '';
96 $this->filterers = array();
97 }
98
99 function showTV(){
100 if($this->SeparateDV){
101 $this->HideTableView = ($this->Permissions[2]==0 ? 1 : 0);
102 }
103 }
104
105 function hideTV(){
106 if($this->SeparateDV){
107 $this->HideTableView = 1;
108 }
109 }
110
111 function set_headers(){
112 @header('Content-Type: text/html; charset=' . datalist_db_encoding);
113 @header('X-Frame-Options: SAMEORIGIN'); // prevent iframing by other sites to prevent clickjacking
114 }
115
116 function Render(){
117 // get post and get variables
118 global $Translation;
119
120 $adminConfig = config('adminConfig');
121
122 $FiltersPerGroup = 4;
123 $buttonWholeWidth = 136;
124
125 $current_view = ''; /* TV, DV, TVDV, TVP, DVP, Filters */
126
127 $Embedded = intval($_REQUEST['Embedded']);
128 $AutoClose = intval($_REQUEST['AutoClose']);
129
130 $SortField = $_REQUEST["SortField"];
131 $SortDirection = $_REQUEST["SortDirection"];
132 $FirstRecord = $_REQUEST["FirstRecord"];
133 $ScrollUp_y = $_REQUEST["ScrollUp_y"];
134 $ScrollDn_y = $_REQUEST["ScrollDn_y"];
135 $Previous_x = $_REQUEST["Previous_x"];
136 $Next_x = $_REQUEST["Next_x"];
137 $Filter_x = $_REQUEST["Filter_x"];
138 $SaveFilter_x = $_REQUEST["SaveFilter_x"];
139 $NoFilter_x = $_REQUEST["NoFilter_x"];
140 $CancelFilter = $_REQUEST["CancelFilter"];
141 $ApplyFilter = $_REQUEST["ApplyFilter"];
142 $Search_x = $_REQUEST["Search_x"];
143 $SearchString = (get_magic_quotes_gpc() ? stripslashes($_REQUEST['SearchString']) : $_REQUEST['SearchString']);
144 $CSV_x = $_REQUEST["CSV_x"];
145 $Print_x = $_REQUEST['Print_x'];
146 $PrintTV = $_REQUEST['PrintTV'];
147 $PrintDV = $_REQUEST['PrintDV'];
148 $SelectedID = (get_magic_quotes_gpc() ? stripslashes($_REQUEST['SelectedID']) : $_REQUEST['SelectedID']);
149 $insert_x = $_REQUEST['insert_x'];
150 $update_x = $_REQUEST['update_x'];
151 $delete_x = $_REQUEST['delete_x'];
152 $SkipChecks = $_REQUEST['confirmed'];
153 $deselect_x = $_REQUEST['deselect_x'];
154 $addNew_x = $_REQUEST['addNew_x'];
155 $dvprint_x = $_REQUEST['dvprint_x'];
156 $DisplayRecords = (in_array($_REQUEST['DisplayRecords'], array('user', 'group')) ? $_REQUEST['DisplayRecords'] : 'all');
157 list($FilterAnd, $FilterField, $FilterOperator, $FilterValue) = $this->validate_filters($_REQUEST, $FiltersPerGroup);
158 $record_selector = array();
159 if(isset($_REQUEST['record_selector']) && is_array($_REQUEST['record_selector']))
160 $record_selector = $_REQUEST['record_selector'];
161
162
163 $mi = getMemberInfo();
164
165 // validate user inputs
166 if(!preg_match('/^\s*[1-9][0-9]*\s*(asc|desc)?(\s*,\s*[1-9][0-9]*\s*(asc|desc)?)*$/i', $SortField)){
167 $SortField = '';
168 }
169 if(!preg_match('/^(asc|desc)$/i', $SortDirection)){
170 $SortDirection = '';
171 }
172
173 if(!$this->AllowDelete){
174 $delete_x = '';
175 }
176 if(!$this->AllowDeleteOfParents){
177 $SkipChecks = '';
178 }
179 if(!$this->AllowInsert){
180 $insert_x = '';
181 $addNew_x = '';
182 }
183 if(!$this->AllowUpdate){
184 $update_x = '';
185 }
186 if(!$this->AllowFilters){
187 $Filter_x = '';
188 }
189 if(!$this->AllowPrinting){
190 $Print_x = '';
191 $PrintTV = '';
192 }
193 if(!$this->QuickSearch){
194 $SearchString = '';
195 }
196 if(!$this->AllowCSV){
197 $CSV_x = '';
198 }
199
200 // enforce record selection if user has edit/delete permissions on the current table
201 $AllowPrintDV=1;
202 $this->Permissions=getTablePermissions($this->TableName);
203 if($this->Permissions[3] || $this->Permissions[4]){ // current user can edit or delete?
204 $this->AllowSelection = 1;
205 }elseif(!$this->AllowSelection){
206 $SelectedID='';
207 $AllowPrintDV=0;
208 $PrintDV='';
209 }
210
211 if(!$this->AllowSelection || !$SelectedID){ $dvprint_x=''; }
212
213 $this->QueryFieldsIndexed=reIndex($this->QueryFieldsFilters);
214
215 // determine type of current view: TV, DV, TVDV, TVP, DVP or Filters?
216 if($this->SeparateDV){
217 $current_view = 'TV';
218 if($Print_x != '' || $PrintTV != '') $current_view = 'TVP';
219 elseif($dvprint_x != '' || $PrintDV != '') $current_view = 'DVP';
220 elseif($Filter_x != '') $current_view = 'Filters';
221 elseif(($SelectedID && !$deselect_x && !$delete_x) || $addNew_x != '') $current_view = 'DV';
222 }else{
223 $current_view = 'TVDV';
224 if($Print_x != '' || $PrintTV != '') $current_view = 'TVP';
225 elseif($dvprint_x != '' || $PrintDV != '') $current_view = 'DVP';
226 elseif($Filter_x != '') $current_view = 'Filters';
227 }
228
229 $this->HTML .= '<div class="row"><div class="col-xs-12">';
230 $this->HTML .= '<form ' . (datalist_image_uploads_exist ? 'enctype="multipart/form-data" ' : '') . 'method="post" name="myform" action="' . $this->ScriptFileName . '">';
231 if($Embedded) $this->HTML .= '<input name="Embedded" value="1" type="hidden">';
232 if($AutoClose) $this->HTML .= '<input name="AutoClose" value="1" type="hidden">';
233 $this->HTML .= '<script>';
234 $this->HTML .= 'function enterAction(){';
235 $this->HTML .= ' if($j("input[name=SearchString]:focus").length){ $j("#Search").click(); }';
236 $this->HTML .= ' return false;';
237 $this->HTML .= '}';
238 $this->HTML .= '</script>';
239 $this->HTML .= '<input id="EnterAction" type="submit" style="position: absolute; left: 0px; top: -250px;" onclick="return enterAction();">';
240
241 $this->ContentType='tableview'; // default content type
242
243 if($PrintTV != ''){
244 $Print_x = 1;
245 $_REQUEST['Print_x'] = 1;
246 }
247
248 // handle user commands ...
249 if($deselect_x != ''){
250 $SelectedID = '';
251 $this->showTV();
252 }
253
254 elseif($insert_x != ''){
255 $SelectedID = call_user_func($this->TableName.'_insert');
256
257 // redirect to a safe url to avoid refreshing and thus
258 // insertion of duplicate records.
259 $url = $this->RedirectAfterInsert;
260 $insert_status = 'record-added-ok=' . rand();
261 if(!$SelectedID) $insert_status = 'record-added-error=' . rand();
262
263 // compose filters and sorting
264 foreach($this->filterers as $filterer => $caption){
265 if($_REQUEST['filterer_' . $filterer] != '') $filtersGET .= '&filterer_' . $filterer . '=' . urlencode($_REQUEST['filterer_' . $filterer]);
266 }
267 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
268 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
269 $filtersGET .= "&FilterAnd[{$i}]={$FilterAnd[$i]}&FilterField[{$i}]={$FilterField[$i]}&FilterOperator[{$i}]={$FilterOperator[$i]}&FilterValue[{$i}]=" . urlencode($FilterValue[$i]);
270 }
271 }
272 if($Embedded) $filtersGET .= '&Embedded=1&SelectedID=' . urlencode($SelectedID);
273 if($AutoClose) $filtersGET .= '&AutoClose=1';
274 $filtersGET .= "&SortField={$SortField}&SortDirection={$SortDirection}&FirstRecord={$FirstRecord}";
275 $filtersGET .= "&DisplayRecords={$DisplayRecords}";
276 $filtersGET .= '&SearchString=' . urlencode($SearchString);
277 $filtersGET = substr($filtersGET, 1); // remove initial &
278
279 if($url){
280 /* if designer specified a redirect-after-insert url */
281 $url .= (strpos($url, '?') !== false ? '&' : '?') . $insert_status;
282 $url .= (strpos($url, $this->ScriptFileName) !== false ? "&{$filtersGET}" : '');
283 $url = str_replace("#ID#", urlencode($SelectedID), $url);
284 }else{
285 /* if no redirect-after-insert url, use default */
286 $url = "{$this->ScriptFileName}?{$insert_status}&{$filtersGET}";
287
288 /* if DV and TV in same page, select new record */
289 if(!$this->SeparateDV) $url .= '&SelectedID=' . urlencode($SelectedID);
290 }
291
292 @header('Location: ' . $url);
293 $this->HTML .= "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;url=" . $url ."\">";
294
295 return;
296 }
297
298 elseif($delete_x != ''){
299 $delete_res = call_user_func($this->TableName.'_delete', $SelectedID, $this->AllowDeleteOfParents, $SkipChecks);
300 // handle ajax delete requests
301 if(is_ajax()){
302 die($delete_res ? $delete_res : 'OK');
303 }
304
305 if($delete_res){
306 //$_REQUEST['record-deleted-error'] = 1;
307 $this->HTML .= showNotifications($delete_res, 'alert alert-danger', false);
308 $this->hideTV();
309 $current_view = ($this->SeparateDV ? 'DV' : 'TVDV');
310 }else{
311 $_REQUEST['record-deleted-ok'] = 1;
312 $SelectedID = '';
313 $this->showTV();
314
315 /* close window if embedded */
316 if($Embedded){
317 $this->HTML .= '<script>$j(function(){ setTimeout(function(){ AppGini.closeParentModal(); }, 2000); })</script>';
318 }
319 }
320 }
321
322 elseif($update_x != ''){
323 $updated = call_user_func($this->TableName.'_update', $SelectedID);
324
325 $update_status = 'record-updated-ok=' . rand();
326 if($updated === false) $update_status = 'record-updated-error=' . rand();
327
328 // compose filters and sorting
329 foreach($this->filterers as $filterer => $caption){
330 if($_REQUEST['filterer_' . $filterer] != '') $filtersGET .= '&filterer_' . $filterer . '=' . urlencode($_REQUEST['filterer_' . $filterer]);
331 }
332 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
333 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
334 $filtersGET .= "&FilterAnd[{$i}]={$FilterAnd[$i]}&FilterField[{$i}]={$FilterField[$i]}&FilterOperator[{$i}]={$FilterOperator[$i]}&FilterValue[{$i}]=" . urlencode($FilterValue[$i]);
335 }
336 }
337 $filtersGET .= "&SortField={$SortField}&SortDirection={$SortDirection}&FirstRecord={$FirstRecord}&Embedded={$Embedded}";
338 if($AutoClose) $filtersGET .= '&AutoClose=1';
339 $filtersGET .= "&DisplayRecords={$DisplayRecords}";
340 $filtersGET .= '&SearchString=' . urlencode($SearchString);
341 $filtersGET = substr($filtersGET, 1); // remove initial &
342
343 $redirectUrl = $this->ScriptFileName . '?SelectedID=' . urlencode($SelectedID) . '&' . $filtersGET . '&' . $update_status;
344 @header("Location: $redirectUrl");
345 $this->HTML .= '<META HTTP-EQUIV="Refresh" CONTENT="0;url='.$redirectUrl.'">';
346 return;
347 }
348
349 elseif($addNew_x != ''){
350 $SelectedID='';
351 $this->hideTV();
352 }
353
354 elseif($Print_x != ''){
355 // print code here ....
356 $this->AllowNavigation = 0;
357 $this->AllowSelection = 0;
358 }
359
360 elseif($SaveFilter_x != '' && $this->AllowSavingFilters){
361 $filter_link = $_SERVER['HTTP_REFERER'] . '?SortField=' . urlencode($SortField) . '&SortDirection=' . $SortDirection . '&';
362 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
363 if(($FilterField[$i] != '' || $i == 1) && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
364 $filter_link .= urlencode("FilterAnd[$i]") . '=' . urlencode($FilterAnd[$i]) . '&';
365 $filter_link .= urlencode("FilterField[$i]") . '=' . urlencode($FilterField[$i]) . '&';
366 $filter_link .= urlencode("FilterOperator[$i]") . '=' . urlencode($FilterOperator[$i]) . '&';
367 $filter_link .= urlencode("FilterValue[$i]") . '=' . urlencode($FilterValue[$i]) . '&';
368 }elseif(($i % $FiltersPerGroup == 1) && in_array($FilterAnd[$i], array('and', 'or'))){
369 /* always include the and/or at the beginning of each group */
370 $filter_link .= urlencode("FilterAnd[$i]") . '=' . urlencode($FilterAnd[$i]) . '&';
371 }
372 }
373 $filter_link = substr($filter_link, 0, -1); /* trim last '&' */
374
375 $this->HTML .= '<div id="saved_filter_source_code" class="row"><div class="col-md-6 col-md-offset-3">';
376 $this->HTML .= '<div class="panel panel-info">';
377 $this->HTML .= '<div class="panel-heading"><h3 class="panel-title">' . $Translation["saved filters title"] . "</h3></div>";
378 $this->HTML .= '<div class="panel-body">';
379 $this->HTML .= $Translation["saved filters instructions"];
380 $this->HTML .= '<textarea rows="4" class="form-control vspacer-lg" style="width: 100%;" onfocus="$j(this).select();">' . "<a href=\"{$filter_link}\">Saved filter link<a>" . '</textarea>';
381 $this->HTML .= "<div><a href=\"{$filter_link}\" title=\"" . html_attr($filter_link) . "\">{$Translation['permalink']}</a></div>";
382 $this->HTML .= '<button type="button" class="btn btn-default btn-block vspacer-lg" onclick="$j(\'#saved_filter_source_code\').remove();"><i class="glyphicon glyphicon-remove"></i> ' . $Translation['hide code'] . '</button>';
383 $this->HTML .= '</div>';
384 $this->HTML .= '</div>';
385 $this->HTML .= '</div></div>';
386 }
387
388 elseif($Filter_x != ''){
389 $orderBy = array();
390 if($SortField){
391 $sortFields = explode(',', $SortField);
392 $i=0;
393 foreach($sortFields as $sf){
394 $tob = preg_split('/\s+/', $sf, 2);
395 $orderBy[] = array(trim($tob[0]) => (strtolower(trim($tob[1]))=='desc' ? 'desc' : 'asc'));
396 $i++;
397 }
398 $orderBy[$i-1][$tob[0]] = (strtolower(trim($SortDirection))=='desc' ? 'desc' : 'asc');
399 }
400
401 $currDir=dirname(__FILE__).'/hooks'; // path to hooks folder
402 $uff="{$currDir}/{$this->TableName}.filters.{$mi['username']}.php"; // user-specific filter file
403 $gff="{$currDir}/{$this->TableName}.filters.{$mi['group']}.php"; // group-specific filter file
404 $tff="{$currDir}/{$this->TableName}.filters.php"; // table-specific filter file
405
406 /*
407 if no explicit filter file exists, look for filter files in the hooks folder in this order:
408 1. tablename.filters.username.php ($uff)
409 2. tablename.filters.groupname.php ($gff)
410 3. tablename.filters.php ($tff)
411 */
412 if(!is_file($this->FilterPage)){
413 $this->FilterPage='defaultFilters.php';
414 if(is_file($uff)){
415 $this->FilterPage=$uff;
416 }elseif(is_file($gff)){
417 $this->FilterPage=$gff;
418 }elseif(is_file($tff)){
419 $this->FilterPage=$tff;
420 }
421 }
422
423 if($this->FilterPage!=''){
424 ob_start();
425 @include($this->FilterPage);
426 $out=ob_get_contents();
427 ob_end_clean();
428 $this->HTML .= $out;
429 }
430 // hidden variables ....
431 $this->HTML .= '<input name="SortField" value="'.$SortField.'" type="hidden" />';
432 $this->HTML .= '<input name="SortDirection" type="hidden" value="'.$SortDirection.'" />';
433 $this->HTML .= '<input name="FirstRecord" type="hidden" value="1" />';
434
435 $this->ContentType='filters';
436 $this->set_headers();
437 return;
438 }
439
440 elseif($NoFilter_x != ''){
441 // clear all filters ...
442 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
443 $FilterField[$i] = '';
444 $FilterOperator[$i] = '';
445 $FilterValue[$i] = '';
446 }
447 $DisplayRecords = 'all';
448 $SearchString = '';
449 $FirstRecord = 1;
450
451 // clear filterers
452 foreach($this->filterers as $filterer => $caption){
453 $_REQUEST['filterer_' . $filterer] = '';
454 }
455 }
456
457 elseif($SelectedID){
458 $this->hideTV();
459 }
460
461 // apply lookup filterers to the query
462 foreach($this->filterers as $filterer => $caption){
463 if($_REQUEST['filterer_' . $filterer] != ''){
464 if($this->QueryWhere == '')
465 $this->QueryWhere = "where ";
466 else
467 $this->QueryWhere .= " and ";
468 $this->QueryWhere .= "`{$this->TableName}`.`$filterer`='" . makeSafe($_REQUEST['filterer_' . $filterer]) . "' ";
469 break; // currently, only one filterer can be applied at a time
470 }
471 }
472
473 // apply quick search to the query
474 if($SearchString != ''){
475 if($Search_x!=''){ $FirstRecord=1; }
476
477 if($this->QueryWhere=='')
478 $this->QueryWhere = "where ";
479 else
480 $this->QueryWhere .= " and ";
481
482 foreach($this->QueryFieldsQS as $fName => $fCaption){
483 if(strpos($fName, '<img')===False){
484 $this->QuerySearchableFields[$fName]=$fCaption;
485 }
486 }
487
488 $this->QueryWhere.='('.implode(" LIKE '%".makeSafe($SearchString)."%' or ", array_keys($this->QuerySearchableFields))." LIKE '%".makeSafe($SearchString)."%')";
489 }
490
491
492 // set query filters
493 $QueryHasWhere = 0;
494 if(strpos($this->QueryWhere, 'where ')!==FALSE)
495 $QueryHasWhere = 1;
496
497 $WhereNeedsClosing = 0;
498 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i+=$FiltersPerGroup){ // Number of filters allowed
499 // test current filter group
500 $GroupHasFilters = 0;
501 for($j = 0; $j < $FiltersPerGroup; $j++){
502 if($FilterField[$i+$j] != '' && $this->QueryFieldsIndexed[($FilterField[$i+$j])] != '' && $FilterOperator[$i+$j] != '' && ($FilterValue[$i+$j] != '' || strpos($FilterOperator[$i+$j], 'empty'))){
503 $GroupHasFilters = 1;
504 break;
505 }
506 }
507
508 if($GroupHasFilters){
509 if(!stristr($this->QueryWhere, "where "))
510 $this->QueryWhere = "where (";
511 elseif($QueryHasWhere){
512 $this->QueryWhere .= " and (";
513 $QueryHasWhere = 0;
514 }
515
516 $this->QueryWhere .= " <FilterGroup> " . $FilterAnd[$i] . " (";
517
518 for($j = 0; $j < $FiltersPerGroup; $j++){
519 if($FilterField[$i+$j] != '' && $this->QueryFieldsIndexed[($FilterField[$i+$j])] != '' && $FilterOperator[$i+$j] != '' && ($FilterValue[$i+$j] != '' || strpos($FilterOperator[$i+$j], 'empty'))){
520 if($FilterAnd[$i+$j]==''){
521 $FilterAnd[$i+$j]='and';
522 }
523 // test for date/time fields
524 $tries = 0; $isDateTime = $isDate = false;
525 $fieldName=str_replace('`', '', $this->QueryFieldsIndexed[($FilterField[$i+$j])]);
526 list($tn, $fn)=explode('.', $fieldName);
527 while(!($res = sql("show columns from `{$tn}` like '{$fn}'", $eo)) && $tries < 2){
528 $tn=substr($tn, 0, -1);
529 $tries++;
530 }
531 if($row = @db_fetch_array($res)){
532 $isDateTime = in_array($row['Type'], array('date', 'time', 'datetime'));
533 $isDate = in_array($row['Type'], array('date', 'datetime'));
534 }
535 // end of test
536 if($FilterOperator[$i+$j]=='is-empty' && !$isDateTime){
537 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " (" . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "='' or " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " is NULL) </FilterItem>";
538 }elseif($FilterOperator[$i+$j]=='is-not-empty' && !$isDateTime){
539 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "!='' </FilterItem>";
540 }elseif($FilterOperator[$i+$j]=='is-empty' && $isDateTime){
541 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " (" . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "=0 or " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " is NULL) </FilterItem>";
542 }elseif($FilterOperator[$i+$j]=='is-not-empty' && $isDateTime){
543 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "!=0 </FilterItem>";
544 }elseif($FilterOperator[$i+$j]=='like' && !strstr($FilterValue[$i+$j], "%") && !strstr($FilterValue[$i+$j], "_")){
545 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " like '%" . makeSafe($FilterValue[$i+$j]) . "%' </FilterItem>";
546 }elseif($FilterOperator[$i+$j]=='not-like' && !strstr($FilterValue[$i+$j], "%") && !strstr($FilterValue[$i+$j], "_")){
547 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " not like '%" . makeSafe($FilterValue[$i+$j]) . "%' </FilterItem>";
548 }elseif($isDate){
549 $dateValue = mysql_datetime($FilterValue[$i + $j]);
550 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " " . $GLOBALS['filter_operators'][$FilterOperator[$i+$j]] . " '$dateValue' </FilterItem>";
551 }else{
552 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " " . $GLOBALS['filter_operators'][$FilterOperator[$i+$j]] . " '" . makeSafe($FilterValue[$i+$j]) . "' </FilterItem>";
553 }
554 }
555 }
556
557 $this->QueryWhere .= ") </FilterGroup>";
558 $WhereNeedsClosing = 1;
559 }
560 }
561
562 if($WhereNeedsClosing)
563 $this->QueryWhere .= ")";
564
565 // set query sort
566 if(!stristr($this->QueryOrder, "order by ") && $SortField != '' && $this->AllowSorting){
567 $actualSortField = $SortField;
568 foreach($this->SortFields as $fieldNum => $fieldSort){
569 $actualSortField = str_replace(" $fieldNum ", " $fieldSort ", " $actualSortField ");
570 $actualSortField = str_replace(",$fieldNum ", ",$fieldSort ", " $actualSortField ");
571 }
572 $this->QueryOrder = "order by $actualSortField $SortDirection";
573 }
574
575 // clean up query
576 $this->QueryWhere = str_replace('( <FilterGroup> and ', '( ', $this->QueryWhere);
577 $this->QueryWhere = str_replace('( <FilterGroup> or ', '( ', $this->QueryWhere);
578 $this->QueryWhere = str_replace('( <FilterItem> and ', '( ', $this->QueryWhere);
579 $this->QueryWhere = str_replace('( <FilterItem> or ', '( ', $this->QueryWhere);
580 $this->QueryWhere = str_replace('<FilterGroup>', '', $this->QueryWhere);
581 $this->QueryWhere = str_replace('</FilterGroup>', '', $this->QueryWhere);
582 $this->QueryWhere = str_replace('<FilterItem>', '', $this->QueryWhere);
583 $this->QueryWhere = str_replace('</FilterItem>', '', $this->QueryWhere);
584
585 // if no 'order by' clause found, apply default sorting if specified
586 if($this->DefaultSortField != '' && $this->QueryOrder == ''){
587 $this->QueryOrder="order by ".$this->DefaultSortField." ".$this->DefaultSortDirection;
588 }
589
590 // get count of matching records ...
591 $TempQuery = 'SELECT count(1) from '.$this->QueryFrom.' '.$this->QueryWhere;
592 $RecordCount = sqlValue($TempQuery);
593 $FieldCountTV = count($this->QueryFieldsTV);
594 $FieldCountCSV = count($this->QueryFieldsCSV);
595 $FieldCountFilters = count($this->QueryFieldsFilters);
596 if(!$RecordCount){
597 $FirstRecord=1;
598 }
599
600 // Output CSV on request
601 if($CSV_x != ''){
602 $this->HTML = '';
603 if(datalist_db_encoding == 'UTF-8') $this->HTML = "\xEF\xBB\xBF"; // BOM characters for UTF-8 output
604
605 // execute query for CSV output
606 $fieldList='';
607 foreach($this->QueryFieldsCSV as $fn=>$fc)
608 $fieldList.="$fn as `$fc`, ";
609 $fieldList=substr($fieldList, 0, -2);
610 $csvQuery = 'SELECT '.$fieldList.' from '.$this->QueryFrom.' '.$this->QueryWhere.' '.$this->QueryOrder;
611
612 // hook: table_csv
613 if(function_exists($this->TableName.'_csv')){
614 $args = array();
615 $mq = call_user_func_array($this->TableName . '_csv', array($csvQuery, $mi, &$args));
616 $csvQuery = ($mq ? $mq : $csvQuery);
617 }
618
619 $result = sql($csvQuery, $eo);
620
621 // output CSV field names
622 for($i = 0; $i < $FieldCountCSV; $i++)
623 $this->HTML .= "\"" . db_field_name($result, $i) . "\"" . $this->CSVSeparator;
624 $this->HTML .= "\n\n";
625
626 // output CSV data
627 while($row = db_fetch_row($result)){
628 for($i = 0; $i < $FieldCountCSV; $i++)
629 $this->HTML .= "\"" . str_replace(array("\r\n", "\r", "\n", '"'), array(' ', ' ', ' ', '""'), strip_tags($row[$i])) . "\"" . $this->CSVSeparator;
630 $this->HTML .= "\n\n";
631 }
632 $this->HTML = str_replace($this->CSVSeparator . "\n\n", "\n", $this->HTML);
633 $this->HTML = substr($this->HTML, 0, - 1);
634
635 // clean any output buffers
636 while(@ob_end_clean());
637
638 // output CSV HTTP headers ...
639 header('HTTP/1.1 200 OK');
640 header('Date: ' . @date("D M j G:i:s T Y"));
641 header('Last-Modified: ' . @date("D M j G:i:s T Y"));
642 header("Content-Type: application/force-download");
643 header("Content-Length: " . (string)(strlen($this->HTML)));
644 header("Content-Transfer-Encoding: Binary");
645 header("Content-Disposition: attachment; filename=$this->TableName.csv");
646
647 // send output and quit script
648 echo $this->HTML;
649 exit;
650 }
651 $t = time(); // just a random number for any purpose ...
652
653 // should SelectedID be reset on clicking TV buttons?
654 $resetSelection = ($this->SeparateDV ? "document.myform.SelectedID.value = '';" : "document.myform.writeAttribute('novalidate', 'novalidate');");
655
656 if($current_view == 'DV' && !$Embedded){
657 $this->HTML .= '<div class="page-header">';
658 $this->HTML .= '<h1>';
659 $this->HTML .= '<a style="text-decoration: none; color: inherit;" href="' . $this->TableName . '_view.php"><img src="' . $this->TableIcon . '"> ' . $this->TableTitle . '</a>';
660 /* show add new button if user can insert and there is a selected record */
661 if($SelectedID && $this->Permissions[1] && $this->SeparateDV && $this->AllowInsert){
662 $this->HTML .= ' <button type="submit" id="addNew" name="addNew_x" value="1" class="btn btn-success"><i class="glyphicon glyphicon-plus-sign"></i> ' . $Translation['Add New'] . '</button>';
663 }
664 $this->HTML .= '</h1>';
665 $this->HTML .= '</div>';
666 }
667
668 // quick search and TV action buttons
669 if(!$this->HideTableView && !($dvprint_x && $this->AllowSelection && $SelectedID) && !$PrintDV){
670 $buttons_all = $quick_search_html = '';
671
672 if($Print_x == ''){
673
674 // display 'Add New' icon
675 if($this->Permissions[1] && $this->SeparateDV && $this->AllowInsert){
676 $buttons_all .= '<button type="submit" id="addNew" name="addNew_x" value="1" class="btn btn-success"><i class="glyphicon glyphicon-plus-sign"></i> ' . $Translation['Add New'] . '</button>';
677 $buttonsCount++;
678 }
679
680 // display Print icon
681 if($this->AllowPrinting){
682 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="Print_x" id="Print" value="1" class="btn btn-default"><i class="glyphicon glyphicon-print"></i> ' . $Translation['Print Preview'] . '</button>';
683 $buttonsCount++;
684 }
685
686 // display CSV icon
687 if($this->AllowCSV){
688 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="CSV_x" id="CSV" value="1" class="btn btn-default"><i class="glyphicon glyphicon-download-alt"></i> ' . $Translation['CSV'] . '</button>';
689 $buttonsCount++;
690 }
691
692 // display Filter icon
693 if($this->AllowFilters){
694 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="Filter_x" id="Filter" value="1" class="btn btn-default"><i class="glyphicon glyphicon-filter"></i> ' . $Translation['filter'] . '</button>';
695 $buttonsCount++;
696 }
697
698 // display Show All icon
699 if(($this->AllowFilters)){
700 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="NoFilter_x" id="NoFilter" value="1" class="btn btn-default"><i class="glyphicon glyphicon-remove-circle"></i> ' . $Translation['Reset Filters'] . '</button>';
701 $buttonsCount++;
702 }
703
704 $quick_search_html .= quick_search_html($SearchString, $this->QuickSearchText, $this->SeparateDV);
705 }else{
706 $buttons_all .= '<button class="btn btn-primary" type="button" id="sendToPrinter" onClick="window.print();"><i class="glyphicon glyphicon-print"></i> ' . $Translation['Print'] . '</button>';
707 $buttons_all .= '<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-remove-circle"></i> ' . $Translation['Cancel Printing'] . '</button>';
708 }
709
710 /* if user can print DV, add action to 'More' menu */
711 $selected_records_more = array();
712
713 if($AllowPrintDV){
714 $selected_records_more[] = array(
715 'function' => ($this->SeparateDV ? 'print_multiple_dv_sdv' : 'print_multiple_dv_tvdv'),
716 'title' => $Translation['Print Preview Detail View'],
717 'icon' => 'print'
718 );
719 }
720
721 /* if user can mass-delete selected records, add action to 'More' menu */
722 if($this->AllowMassDelete && $this->AllowDelete){
723 $selected_records_more[] = array(
724 'function' => 'mass_delete',
725 'title' => $Translation['Delete'],
726 'icon' => 'trash',
727 'class' => 'text-danger'
728 );
729 }
730
731 /* if user is admin, add 'Change owner' action to 'More' menu */
732 /* also, add help link for adding more actions */
733 if($mi['admin']){
734 $selected_records_more[] = array(
735 'function' => 'mass_change_owner',
736 'title' => $Translation['Change owner'],
737 'icon' => 'user'
738 );
739 $selected_records_more[] = array(
740 'function' => 'add_more_actions_link',
741 'title' => $Translation['Add more actions'],
742 'icon' => 'question-sign',
743 'class' => 'text-info'
744 );
745 }
746
747 /* user-defined actions ... should be set in the {tablename}_batch_actions() function in hooks/{tablename}.php */
748 $user_actions = array();
749 if(function_exists($this->TableName.'_batch_actions')){
750 $args = array();
751 $user_actions = call_user_func_array($this->TableName . '_batch_actions', array(&$args));
752 if(is_array($user_actions) && count($user_actions)){
753 $selected_records_more = array_merge($selected_records_more, $user_actions);
754 }
755 }
756
757 $actual_more_count = 0;
758 $more_menu = $more_menu_js = '';
759 if(count($selected_records_more)){
760 $more_menu .= '<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" id="selected_records_more"><i class="glyphicon glyphicon-check"></i> ' . $Translation['More'] . ' <span class="caret"></span></button>';
761 $more_menu .= '<ul class="dropdown-menu" role="menu">';
762 foreach($selected_records_more as $action){
763 if(!$action['function'] || !$action['title']) continue;
764 $action['class'] = (!isset($action['class']) ? '' : $action['class']);
765 $action['icon'] = (!isset($action['icon']) ? '' : $action['icon']);
766 $actual_more_count++;
767 $more_menu .= '<li>' .
768 '<a href="#" id="selected_records_' . $action['function'] . '">' .
769 '<span class="' . $action['class'] . '">' .
770 ($action['icon'] ? '<i class="glyphicon glyphicon-' . $action['icon'] . '"></i> ' : '') .
771 $action['title'] .
772 '</span>' .
773 '</a>' .
774 '</li>';
775
776 // on clicking an action, call its js handler function, passing the current table name and an array of selected IDs to it
777 $more_menu_js .= "jQuery('[id=selected_records_{$action['function']}]').click(function(){ {$action['function']}('{$this->TableName}', get_selected_records_ids()); return false; });";
778 }
779 $more_menu .= '</ul>';
780 }
781
782 if($Embedded){
783 $this->HTML .= '<script>$j(function(){ $j(\'[id^=notification-]\').parent().css({\'margin-top\': \'15px\', \'margin-bottom\': \'0\'}); })</script>';
784 }else{
785 $this->HTML .= '<div class="page-header">';
786 $this->HTML .= '<h1>';
787 $this->HTML .= '<div class="row">';
788 $this->HTML .= '<div class="col-sm-8">';
789 $this->HTML .= '<a style="text-decoration: none; color: inherit;" href="' . $this->TableName . '_view.php"><img src="' . $this->TableIcon . '"> ' . $this->TableTitle . '</a>';
790 /* show add new button if user can insert and there is a selected record */
791 if($SelectedID && $this->Permissions[1] && !$this->SeparateDV && $this->AllowInsert){
792 $this->HTML .= ' <button type="submit" id="addNew" name="addNew_x" value="1" class="btn btn-success"><i class="glyphicon glyphicon-plus-sign"></i> ' . $Translation['Add New'] . '</button>';
793 }
794 $this->HTML .= '</div>';
795 if($this->QuickSearch){
796 $this->HTML .= '<div class="col-sm-4">';
797 $this->HTML .= $quick_search_html;
798 $this->HTML .= '</div>';
799 }
800 $this->HTML .= '</div>';
801 $this->HTML .= '</h1>';
802 $this->HTML .= '</div>';
803
804 $this->HTML .= '<div id="top_buttons" class="hidden-print">';
805 /* .all_records: container for buttons that don't need a selection */
806 /* .selected_records: container for buttons that need a selection */
807 $this->HTML .= '<div class="btn-group btn-group-lg visible-md visible-lg all_records pull-left">' . $buttons_all . '</div>';
808 $this->HTML .= '<div class="btn-group btn-group-lg visible-md visible-lg selected_records hidden pull-left hspacer-lg">' . ($actual_more_count ? $more_menu : '') . '</div>';
809 $this->HTML .= '<div class="btn-group-vertical btn-group-lg visible-xs visible-sm all_records">' . $buttons_all . '</div>';
810 $this->HTML .= '<div class="btn-group-vertical btn-group-lg visible-xs visible-sm selected_records hidden vspacer-lg">' . ($actual_more_count ? $more_menu : '') . '</div>';
811 $this->HTML .= $this->tv_tools();
812 $this->HTML .= '<p></p>';
813 $this->HTML .= '</div>';
814
815 $this->HTML .= '<div class="row"><div class="table_view col-xs-12 ' . $this->TVClasses . '">';
816 }
817
818 if($Print_x != ''){
819 /* fix top margin for print-preview */
820 $this->HTML .= '<style>body{ padding-top: 0 !important; }</style>';
821
822 /* disable links inside table body to prevent printing their href */
823 $this->HTML .= '<script>jQuery(function(){ jQuery("tbody a").removeAttr("href").removeAttr("rel"); });</script>';
824 }
825
826 // script for focusing into the search box on loading the page
827 // and for declaring record action handlers
828 $this->HTML .= '<script>jQuery(function(){ jQuery("input[name=SearchString]").focus(); ' . $more_menu_js . ' });</script>';
829
830 }
831
832 // begin table and display table title
833 if(!$this->HideTableView && !($dvprint_x && $this->AllowSelection && $SelectedID) && !$PrintDV && !$Embedded){
834 $this->HTML .= '<div class="table-responsive"><table class="table table-striped table-bordered table-hover">';
835
836 $this->HTML .= '<thead><tr>';
837 if(!$Print_x) $this->HTML .= '<th style="width: 18px;" class="text-center"><input class="hidden-print" type="checkbox" title="' . html_attr($Translation['Select all records']) . '" id="select_all_records"></th>';
838 // Templates
839 $rowTemplate = $selrowTemplate = '';
840 if($this->Template){
841 $rowTemplate = @file_get_contents('./' . $this->Template);
842 if($rowTemplate && $this->SelectedTemplate){
843 $selrowTemplate = @file_get_contents('./' . $this->SelectedTemplate);
844 }
845 }
846
847 // process translations
848 if($rowTemplate){
849 foreach($Translation as $symbol=>$trans){
850 $rowTemplate=str_replace("<%%TRANSLATION($symbol)%%>", $trans, $rowTemplate);
851 }
852 }
853 if($selrowTemplate){
854 foreach($Translation as $symbol=>$trans){
855 $selrowTemplate=str_replace("<%%TRANSLATION($symbol)%%>", $trans, $selrowTemplate);
856 }
857 }
858 // End of templates
859
860 // $this->ccffv: map $FilterField values to field captions as stored in ColCaption
861 $this->ccffv = array();
862 foreach($this->ColCaption as $captionIndex => $caption){
863 $ffv = 1;
864 foreach($this->QueryFieldsFilters as $uselessKey => $filterCaption){
865 if($caption == $filterCaption){
866 $this->ccffv[$captionIndex] = $ffv;
867 }
868 $ffv++;
869 }
870 }
871
872 // display table headers
873 $forceHeaderWidth = false;
874 if($rowTemplate=='' || $this->ShowTableHeader){
875 for($i = 0; $i < count($this->ColCaption); $i++){
876 /* Sorting icon and link */
877 $sort1 = $sort2 = $filterHint = '';
878 if($this->AllowSorting == 1){
879 if($current_view != 'TVP'){
880 $sort1 = "<a href=\"{$this->ScriptFileName}?SortDirection=asc&SortField=".($this->ColNumber[$i])."\" onClick=\"$resetSelection document.myform.NoDV.value=1; document.myform.SortDirection.value='asc'; document.myform.SortField.value = '".($this->ColNumber[$i])."'; document.myform.submit(); return false;\" class=\"TableHeader\">";
881 $sort2 = "</a>";
882 }
883 if($this->ColNumber[$i] == $SortField){
884 $SortDirection = ($SortDirection == "asc" ? "desc" : "asc");
885 if($current_view != 'TVP')
886 $sort1 = "<a href=\"{$this->ScriptFileName}?SortDirection=$SortDirection&SortField=".($this->ColNumber[$i])."\" onClick=\"$resetSelection document.myform.NoDV.value=1; document.myform.SortDirection.value='$SortDirection'; document.myform.SortField.value = ".($this->ColNumber[$i])."; document.myform.submit(); return false;\" class=\"TableHeader\">";
887 $sort2 = " <i class=\"text-warning glyphicon glyphicon-sort-by-attributes" . ($SortDirection == 'desc' ? '' : '-alt') . "\"></i>{$sort2}";
888 $SortDirection = ($SortDirection == "asc" ? "desc" : "asc");
889 }
890 }
891
892 /* Filtering icon and hint */
893 if($this->AllowFilters && is_array($FilterField)){
894 // check to see if there is any filter applied on the current field
895 if(isset($this->ccffv[$i]) && in_array($this->ccffv[$i], $FilterField)){
896 // render filter icon
897 $filterHint = ' <button type="submit" class="btn btn-default btn-xs' . ($current_view == 'TVP' ? ' disabled' : '') . '" name="Filter_x" value="1" title="'.html_attr($Translation['filtered field']).'"><i class="glyphicon glyphicon-filter"></i></button>';
898 }
899 }
900
901 $this->HTML .= "\t<th class=\"{$this->TableName}-{$this->ColFieldName[$i]}\" " . ($forceHeaderWidth ? ' style="width: ' . ($this->ColWidth[$i] ? $this->ColWidth[$i] : 100) . 'px;"' : '') . ">{$sort1}{$this->ColCaption[$i]}{$sort2}{$filterHint}</th>\n";
902 }
903 }elseif($current_view != 'TVP'){
904 // Display a Sort by drop down
905 $this->HTML .= "\t<th class=\"hidden-print\" colspan=\"" . (count($this->ColCaption)) . "\">";
906 $this->HTML .= "\t<div class=\"pull-right\" id=\"order-by-selector\">";
907
908 if($this->AllowSorting == 1){
909 $sortCombo = new Combo;
910 for($i=0; $i < count($this->ColCaption); $i++){
911 $sortCombo->ListItem[] = $this->ColCaption[$i];
912 $sortCombo->ListData[] = $this->ColNumber[$i];
913 }
914 $sortCombo->SelectName = "FieldsList";
915 $sortCombo->SelectedData = $SortField;
916 $sortCombo->Class = '';
917 $sortCombo->SelectedClass = '';
918 $sortCombo->Render();
919 $sortby_dropdown = $sortCombo->HTML;
920 $sortby_dropdown = str_replace('<select ', "<select onChange=\"document.myform.SortDirection.value='$SortDirection'; document.myform.SortField.value=document.myform.FieldsList.value; document.myform.NoDV.value=1; document.myform.submit();\" ", $sortby_dropdown);
921 if($SortField){
922 $SortDirection = ($SortDirection == 'desc' ? 'asc' : 'desc');
923 $sort_class = ($SortDirection == 'asc' ? 'sort-by-attributes-alt' : 'sort-by-attributes');
924 $sort = "<a href=\"javascript: document.myform.NoDV.value=1; document.myform.SortDirection.value='{$SortDirection}'; document.myform.SortField.value='{$SortField}'; document.myform.submit();\" class=TableHeader><i class=\"text-warning glyphicon glyphicon-{$sort_class}\"></i></a>";
925 $SortDirection = ($SortDirection == 'desc' ? 'asc' : 'desc');
926 }else{
927 $sort = '';
928 }
929
930 $sortby_sep = '<span class="hspacer-md"></span>';
931
932 $this->HTML .= "{$Translation['order by']}{$sortby_sep}{$sortby_dropdown}{$sortby_sep}{$sort}{$sortby_sep}";
933 }
934 $this->HTML .= "</div><style>#s2id_FieldsList{ min-width: 12em; width: unset !important; }</style></th>\n";
935 }
936
937 // table view navigation code ...
938 if($RecordCount && $this->AllowNavigation && $RecordCount>$this->RecordsPerPage){
939 while($FirstRecord > $RecordCount)
940 $FirstRecord -= $this->RecordsPerPage;
941
942 if($FirstRecord == '' || $FirstRecord < 1) $FirstRecord = 1;
943
944 if($Previous_x != ''){
945 $FirstRecord -= $this->RecordsPerPage;
946 if($FirstRecord <= 0)
947 $FirstRecord = 1;
948 }elseif($Next_x != ''){
949 $FirstRecord += $this->RecordsPerPage;
950 if($FirstRecord > $RecordCount)
951 $FirstRecord = $RecordCount - ($RecordCount % $this->RecordsPerPage) + 1;
952 if($FirstRecord > $RecordCount)
953 $FirstRecord = $RecordCount - $this->RecordsPerPage + 1;
954 if($FirstRecord <= 0)
955 $FirstRecord = 1;
956 }
957
958 }elseif($RecordCount){
959 $FirstRecord = 1;
960 $this->RecordsPerPage = 2000; // a limit on max records in print preview to avoid performance drops
961 }
962 // end of table view navigation code
963 $this->HTML .= "\n\t</tr>\n\n</thead>\n\n<tbody><!-- tv data below -->\n";
964
965 $i = 0;
966 if($RecordCount){
967 $i = $FirstRecord;
968 // execute query for table view
969 $query_fields = array();
970 foreach($this->QueryFieldsTV as $fn => $fc)
971 $query_fields[] = "$fn as `$fc`";
972 $fieldList = implode(', ', $query_fields);
973
974 if($this->PrimaryKey)
975 $fieldList .= ", $this->PrimaryKey as '" . str_replace('`', '', $this->PrimaryKey) . "'";
976
977 $tvQuery = "SELECT {$fieldList} from {$this->QueryFrom} {$this->QueryWhere} {$this->QueryOrder}";
978 $result = sql($tvQuery . " limit " . ($i-1) . ",{$this->RecordsPerPage}", $eo);
979 while(($row = db_fetch_array($result)) && ($i < ($FirstRecord + $this->RecordsPerPage))){
980 /* skip displaying the current record if we're in TVP or multiple DVP and the record is not checked */
981 if(($PrintTV || $Print_x) && count($record_selector) && !in_array($row[$FieldCountTV], $record_selector)) continue;
982
983 $attr_id = html_attr($row[$FieldCountTV]); /* pk value suitable for inserting into html tag attributes */
984 $js_id = addslashes($row[$FieldCountTV]); /* pk value suitable for inserting into js strings */
985
986 /* show record selector except in TVP */
987 if($Print_x != ''){ $this->HTML .= '<tr>'; }
988
989 if(!$Print_x){
990 $this->HTML .= ($SelectedID == $row[$FieldCountTV] ? '<tr class="active">' : '<tr>');
991 $checked = (is_array($record_selector) && in_array($row[$FieldCountTV], $record_selector) ? ' checked' : '');
992 $this->HTML .= "<td class=\"text-center\"><input class=\"hidden-print record_selector\" type=\"checkbox\" id=\"record_selector_{$attr_id}\" name=\"record_selector[]\" value=\"{$attr_id}\"{$checked}></td>";
993 }
994
995 /* apply record templates */
996 if($rowTemplate != ''){
997 $rowTemp = $rowTemplate;
998 if($this->AllowSelection == 1 && $SelectedID == $row[$FieldCountTV] && $selrowTemplate != ''){
999 $rowTemp = $selrowTemplate;
1000 }
1001
1002 if($this->AllowSelection == 1 && $SelectedID != $row[$FieldCountTV]){
1003 $rowTemp = str_replace('<%%SELECT%%>',"<a onclick=\"document.myform.SelectedField.value=this.parentNode.cellIndex; document.myform.SelectedID.value='" . addslashes($row[$FieldCountTV]) . "'; document.myform.submit(); return false;\" href=\"{$this->ScriptFileName}?SelectedID=" . html_attr($row[$FieldCountTV]) . "\" style=\"display: block; padding:0px;\">",$rowTemp);
1004 $rowTemp = str_replace('<%%ENDSELECT%%>','</a>',$rowTemp);
1005 }else{
1006 $rowTemp = str_replace('<%%SELECT%%>', '', $rowTemp);
1007 $rowTemp = str_replace('<%%ENDSELECT%%>', '', $rowTemp);
1008 }
1009
1010 for($j = 0; $j < $FieldCountTV; $j++){
1011 $fieldTVCaption = current(array_slice($this->QueryFieldsTV, $j, 1));
1012
1013 $fd = safe_html($row[$j]);
1014
1015 /*
1016 the TV template could contain field placeholders in the format
1017 <%%FIELD_n%%> or <%%VALUE(Field caption)%%> or <%%HTML_ATTR(field caption)%%>
1018 */
1019 $rowTemp = str_replace("<%%FIELD_$j%%>", thisOr($fd, ''), $rowTemp);
1020 $rowTemp = str_replace("<%%VALUE($fieldTVCaption)%%>", thisOr($fd, ''), $rowTemp);
1021 $rowTemp = str_replace("<%%HTML_ATTR($fieldTVCaption)%%>", html_attr($fd), $rowTemp);
1022
1023 if(strpos($rowTemp, "<%%YOUTUBETHUMB($fieldTVCaption)%%>") !== false) $rowTemp = str_replace("<%%YOUTUBETHUMB($fieldTVCaption)%%>", thisOr(get_embed('youtube', $fd, '', '', 'thumbnail_url'), 'blank.gif'), $rowTemp);
1024 if(strpos($rowTemp, "<%%GOOGLEMAPTHUMB($fieldTVCaption)%%>") !== false) $rowTemp = str_replace("<%%GOOGLEMAPTHUMB($fieldTVCaption)%%>", thisOr(get_embed('googlemap', $fd, '', '', 'thumbnail_url'), 'blank.gif'), $rowTemp);
1025 if(thisOr($fd)==' ' && preg_match('/<a href=".*? .*?<\/a>/i', $rowTemp, $m)){
1026 $rowTemp=str_replace($m[0], '', $rowTemp);
1027 }
1028 }
1029
1030 $this->HTML .= $rowTemp;
1031 $rowTemp = '';
1032
1033 }else{
1034 // default view if no template
1035 for($j = 0; $j < $FieldCountTV; $j++){
1036 if($this->AllowSelection == 1){
1037 $sel1 = "<a href=\"{$this->ScriptFileName}?SelectedID=" . html_attr($row[$FieldCountTV]) . "\" onclick=\"document.myform.SelectedID.value='" . addslashes($row[$FieldCountTV]) . "'; document.myform.submit(); return false;\" style=\"padding:0px;\">";
1038 $sel2 = "</a>";
1039 }else{
1040 $sel1 = '';
1041 $sel2 = '';
1042 }
1043
1044 $this->HTML .= "<td valign=\"top\"><div> {$sel1}{$row[$j]}{$sel2} </div></td>";
1045 }
1046 }
1047 $this->HTML .= "</tr>\n";
1048 $i++;
1049 }
1050 $i--;
1051 }
1052
1053 $this->HTML = preg_replace("/<a href=\"(mailto:)? [^\n]*title=\" \"><\/a>/", ' ', $this->HTML);
1054 $this->HTML = preg_replace("/<a [^>]*>( )*<\/a>/", ' ', $this->HTML);
1055 $this->HTML = preg_replace("/<%%.*%%>/U", ' ', $this->HTML);
1056 // end of data
1057 $this->HTML.='<!-- tv data above -->';
1058 $this->HTML .= "\n</tbody>";
1059
1060 if($Print_x == ''){ // TV
1061 $pagesMenu = '';
1062 if($RecordCount > $this->RecordsPerPage){
1063 $pagesMenuId = "{$this->TableName}_pagesMenu";
1064 $pagesMenu = $Translation['go to page'] . ' <select style="width: 90%; max-width: 8em;" class="input-sm ltr" id="' . $pagesMenuId . '" onChange="document.myform.writeAttribute(\'novalidate\', \'novalidate\'); document.myform.NoDV.value=1; document.myform.FirstRecord.value=(this.value * ' . $this->RecordsPerPage . '+1); document.myform.submit();">';
1065 $pagesMenu .= '</select>';
1066
1067 $pagesMenu .= '<script>';
1068 $pagesMenu .= 'var lastPage = ' . (ceil($RecordCount / $this->RecordsPerPage) - 1) . ';';
1069 $pagesMenu .= 'var currentPage = ' . (($FirstRecord - 1) / $this->RecordsPerPage) . ';';
1070 $pagesMenu .= 'var pagesMenu = document.getElementById("' . $pagesMenuId . '");';
1071 $pagesMenu .= 'var lump = ' . datalist_max_page_lump . ';';
1072
1073 $pagesMenu .= 'if(lastPage <= lump * 3){';
1074 $pagesMenu .= ' addPageNumbers(0, lastPage);';
1075 $pagesMenu .= '}else{';
1076 $pagesMenu .= ' addPageNumbers(0, lump - 1);';
1077 $pagesMenu .= ' if(currentPage < lump) addPageNumbers(lump, currentPage + lump / 2);';
1078 $pagesMenu .= ' if(currentPage >= lump && currentPage < (lastPage - lump)){';
1079 $pagesMenu .= ' addPageNumbers(';
1080 $pagesMenu .= ' Math.max(currentPage - lump / 2, lump),';
1081 $pagesMenu .= ' Math.min(currentPage + lump / 2, lastPage - lump - 1)';
1082 $pagesMenu .= ' );';
1083 $pagesMenu .= ' }';
1084 $pagesMenu .= ' if(currentPage >= (lastPage - lump)) addPageNumbers(currentPage - lump / 2, lastPage - lump - 1);';
1085 $pagesMenu .= ' addPageNumbers(lastPage - lump, lastPage);';
1086 $pagesMenu .= '}';
1087
1088 $pagesMenu .= 'function addPageNumbers(fromPage, toPage){';
1089 $pagesMenu .= ' var ellipsesIndex = 0;';
1090 $pagesMenu .= ' if(fromPage > toPage) return;';
1091 $pagesMenu .= ' if(fromPage > 0){';
1092 $pagesMenu .= ' if(pagesMenu.options[pagesMenu.options.length - 1].text != fromPage){';
1093 $pagesMenu .= ' ellipsesIndex = pagesMenu.options.length;';
1094 $pagesMenu .= ' fromPage--;';
1095 $pagesMenu .= ' }';
1096 $pagesMenu .= ' }';
1097 $pagesMenu .= ' for(i = fromPage; i <= toPage; i++){';
1098 $pagesMenu .= ' var option = document.createElement("option");';
1099 $pagesMenu .= ' option.text = (i + 1);';
1100 $pagesMenu .= ' option.value = i;';
1101 $pagesMenu .= ' if(i == currentPage){ option.selected = "selected"; }';
1102 $pagesMenu .= ' try{';
1103 $pagesMenu .= ' /* for IE earlier than version 8 */';
1104 $pagesMenu .= ' pagesMenu.add(option, pagesMenu.options[null]);';
1105 $pagesMenu .= ' }catch(e){';
1106 $pagesMenu .= ' pagesMenu.add(option, null);';
1107 $pagesMenu .= ' }';
1108 $pagesMenu .= ' }';
1109 $pagesMenu .= ' if(ellipsesIndex > 0){';
1110 $pagesMenu .= ' pagesMenu.options[ellipsesIndex].text = " ... ";';
1111 $pagesMenu .= ' }';
1112 $pagesMenu .= '}';
1113 $pagesMenu .= '</script>';
1114 }
1115
1116 $this->HTML .= "\n\t";
1117
1118 if($i){ // 1 or more records found
1119 $this->HTML .= "<tfoot><tr><td colspan=".(count($this->ColCaption)+1).'>';
1120 $this->HTML .= $Translation['records x to y of z'];
1121 $this->HTML .= '</td></tr></tfoot>';
1122 }
1123
1124 if(!$i){ // no records found
1125 $this->HTML .= "<tfoot><tr><td colspan=".(count($this->ColCaption)+1).'>';
1126 $this->HTML .= '<div class="alert alert-warning">';
1127 $this->HTML .= '<i class="glyphicon glyphicon-warning-sign"></i> ';
1128 $this->HTML .= $Translation['No matches found!'];
1129 $this->HTML .= '</div>';
1130 $this->HTML .= '</td></tr></tfoot>';
1131 }
1132
1133 }else{ // TVP
1134 if($i) $this->HTML .= "\n\t<tfoot><tr><td colspan=".(count($this->ColCaption) + 1). '>' . $Translation['records x to y of z'] . '</td></tr></tfoot>';
1135 if(!$i) $this->HTML .= "\n\t<tfoot><tr><td colspan=".(count($this->ColCaption) + 1). '>' . $Translation['No matches found!'] . '</td></tr></tfoot>';
1136 }
1137
1138 $this->HTML = str_replace("<FirstRecord>", number_format($FirstRecord), $this->HTML);
1139 $this->HTML = str_replace("<LastRecord>", number_format($i), $this->HTML);
1140 $this->HTML = str_replace("<RecordCount>", number_format($RecordCount), $this->HTML);
1141 $tvShown=true;
1142
1143 $this->HTML .= "</table></div>\n";
1144
1145 /* highlight quick search matches */
1146 if($SearchString!='') $this->HTML .= '<script>$j(function(){ $j(".table-responsive td").mark("' . html_attr($SearchString) . '", { className: "text-warning bg-warning", diacritics: false }); })</script>';
1147
1148 if($Print_x == '' && $i){ // TV
1149 $this->HTML .= '<div class="row pagination-section">';
1150 $this->HTML .= '<div class="col-xs-4 col-md-3 col-lg-2 vspacer-lg">';
1151 $this->HTML .= '<button onClick="' . $resetSelection . ' document.myform.NoDV.value = 1; return true;" type="submit" name="Previous_x" id="Previous" value="1" class="btn btn-default btn-block"><i class="glyphicon glyphicon-chevron-left"></i> <span class="hidden-xs">' . $Translation['Previous'] . '</span></button>';
1152 $this->HTML .= '</div>';
1153
1154 $this->HTML .= '<div class="col-xs-4 col-md-4 col-lg-2 col-md-offset-1 col-lg-offset-3 text-center vspacer-lg">';
1155 $this->HTML .= $pagesMenu;
1156 $this->HTML .= '</div>';
1157
1158 $this->HTML .= '<div class="col-xs-4 col-md-3 col-lg-2 col-md-offset-1 col-lg-offset-3 text-right vspacer-lg">';
1159 $this->HTML .= '<button onClick="'.$resetSelection.' document.myform.NoDV.value=1; return true;" type="submit" name="Next_x" id="Next" value="1" class="btn btn-default btn-block"><span class="hidden-xs">' . $Translation['Next'] . '</span> <i class="glyphicon glyphicon-chevron-right"></i></button>';
1160 $this->HTML .= '</div>';
1161 $this->HTML .= '</div>';
1162 }
1163
1164 $this->HTML .= '</div>'; // end of div.table_view
1165 }
1166 /* that marks the end of the TV table */
1167
1168 // hidden variables ....
1169 foreach($this->filterers as $filterer => $caption){
1170 if($_REQUEST['filterer_' . $filterer] != ''){
1171 $this->HTML .= "<input name=\"filterer_{$filterer}\" value=\"" . html_attr($_REQUEST['filterer_' . $filterer]) . "\" type=\"hidden\" />";
1172 break; // currently, only one filterer can be applied at a time
1173 }
1174 }
1175
1176 $this->HTML .= '<!-- possible values for current_view: TV, TVP, DV, DVP, Filters, TVDV -->';
1177 $this->HTML .= '<input name="current_view" id="current_view" value="' . $current_view . '" type="hidden">';
1178 $this->HTML .= '<input name="SortField" value="' . $SortField . '" type="hidden">';
1179 $this->HTML .= '<input name="SelectedID" value="' . html_attr($SelectedID) . '" type="hidden">';
1180 $this->HTML .= '<input name="SelectedField" value="" type="hidden">';
1181 $this->HTML .= '<input name="SortDirection" type="hidden" value="' . $SortDirection . '">';
1182 $this->HTML .= '<input name="FirstRecord" type="hidden" value="' . $FirstRecord . '">';
1183 $this->HTML .= '<input name="NoDV" type="hidden" value="">';
1184 $this->HTML .= '<input name="PrintDV" type="hidden" value="">';
1185 if($this->QuickSearch && !strpos($this->HTML, 'SearchString')) $this->HTML .= '<input name="SearchString" type="hidden" value="' . html_attr($SearchString) . '">';
1186 // hidden variables: filters ...
1187 $FiltersCode = '';
1188 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
1189 if($i%$FiltersPerGroup == 1 && $i != 1 && $FilterAnd[$i] != ''){
1190 $FiltersCode .= "<input name=\"FilterAnd[$i]\" value=\"$FilterAnd[$i]\" type=\"hidden\">\n";
1191 }
1192 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
1193 if(!strstr($FiltersCode, "<input name=\"FilterAnd[{$i}]\" value="))
1194 $FiltersCode .= "<input name=\"FilterAnd[{$i}]\" value=\"{$FilterAnd[$i]}\" type=\"hidden\">\n";
1195 $FiltersCode .= "<input name=\"FilterField[{$i}]\" value=\"{$FilterField[$i]}\" type=\"hidden\">\n";
1196 $FiltersCode .= "<input name=\"FilterOperator[{$i}]\" value=\"{$FilterOperator[$i]}\" type=\"hidden\">\n";
1197 $FiltersCode .= "<input name=\"FilterValue[{$i}]\" value=\"" . html_attr($FilterValue[$i]) . "\" type=\"hidden\">\n";
1198 }
1199 }
1200 $FiltersCode .= "<input name=\"DisplayRecords\" value=\"$DisplayRecords\" type=\"hidden\" />";
1201 $this->HTML .= $FiltersCode;
1202
1203 // display details form ...
1204 if(($this->AllowSelection || $this->AllowInsert || $this->AllowUpdate || $this->AllowDelete) && $Print_x=='' && !$PrintDV){
1205 if(($this->SeparateDV && $this->HideTableView) || !$this->SeparateDV){
1206 $dvCode = call_user_func("{$this->TableName}_form", $SelectedID, $this->AllowUpdate, (($this->HideTableView && $SelectedID) ? 0 : $this->AllowInsert), $this->AllowDelete, $this->SeparateDV, $this->TemplateDV, $this->TemplateDVP);
1207
1208 $this->HTML .= "\n\t<div class=\"col-xs-12 detail_view {$this->DVClasses}\">{$tv_dv_separator}<div class=\"panel panel-default\">{$dvCode}</div></div>";
1209 $this->HTML .= ($this->SeparateDV ? '<input name="SearchString" value="' . html_attr($SearchString) . '" type="hidden">' : '');
1210 if($dvCode){
1211 $this->ContentType = 'detailview';
1212 $dvShown = true;
1213 }
1214 }
1215 }
1216
1217 // display multiple printable detail views
1218 if($PrintDV){
1219 $dvCode = '';
1220 $_REQUEST['dvprint_x'] = 1;
1221
1222 // hidden vars
1223 foreach($this->filterers as $filterer => $caption){
1224 if($_REQUEST['filterer_' . $filterer] != ''){
1225 $this->HTML .= "<input name=\"filterer_{$filterer}\" value=\"" . html_attr($_REQUEST['filterer_' . $filterer]) . "\" type=\"hidden\" />";
1226 break; // currently, only one filterer can be applied at a time
1227 }
1228 }
1229
1230 // count selected records
1231 $selectedRecords = 0;
1232 if(is_array($record_selector)) foreach($record_selector as $id){
1233 $selectedRecords++;
1234 $this->HTML .= '<input type="hidden" name="record_selector[]" value="' . html_attr($id) . '">'."\n";
1235 }
1236
1237 if($selectedRecords && $selectedRecords <= datalist_max_records_dv_print){ // if records selected > {datalist_max_records_dv_print} don't show DV preview to avoid db performance issues.
1238 foreach($record_selector as $id){
1239 $dvCode .= call_user_func($this->TableName . '_form', $id, 0, 0, 0, 1, $this->TemplateDV, $this->TemplateDVP);
1240 }
1241
1242 if($dvCode!=''){
1243 $dvCode = preg_replace('/<input .*?type="?image"?.*?>/', '', $dvCode);
1244 $this->HTML .= $dvCode;
1245 }
1246 }else{
1247 $this->HTML .= error_message($Translation['Maximum records allowed to enable this feature is'] . ' ' . datalist_max_records_dv_print);
1248 $this->HTML .= '<input type="submit" class="print-button" value="'.$Translation['Print Preview Table View'].'">';
1249 }
1250 }
1251
1252 $this->HTML .= "</div></form>";
1253 $this->HTML .= '</div><div class="col-xs-1 md-hidden lg-hidden"></div></div>';
1254
1255 // $this->HTML .= '<font face="garamond">'.html_attr($tvQuery).'</font>'; // uncomment this line for debugging the table view query
1256
1257 if($dvShown && $tvShown) $this->ContentType='tableview+detailview';
1258 if($dvprint_x!='') $this->ContentType='print-detailview';
1259 if($Print_x!='') $this->ContentType='print-tableview';
1260 if($PrintDV!='') $this->ContentType='print-detailview';
1261
1262 // call detail view javascript hook file if found
1263 $dvJSHooksFile=dirname(__FILE__).'/hooks/'.$this->TableName.'-dv.js';
1264 if(is_file($dvJSHooksFile) && ($this->ContentType=='detailview' || $this->ContentType=='tableview+detailview')){
1265 $this->HTML.="\n<script src=\"hooks/{$this->TableName}-dv.js\"></script>\n";
1266 }
1267
1268 $this->set_headers();
1269 return;
1270 }
1271
1272 function validate_filters($req, $FiltersPerGroup = 4, $is_gpc = true){
1273 $fand = (isset($req['FilterAnd']) && is_array($req['FilterAnd']) ? $req['FilterAnd'] : array());
1274 $ffield = (isset($req['FilterField']) && is_array($req['FilterField']) ? $req['FilterField'] : array());
1275 $fop = (isset($req['FilterOperator']) && is_array($req['FilterOperator']) ? $req['FilterOperator'] : array());
1276 $fvalue = (isset($req['FilterValue']) && is_array($req['FilterValue']) ? $req['FilterValue'] : array());
1277
1278 /* make sure FilterAnd is either 'and' or 'or' */
1279 foreach($fand as $i => $f){
1280 if($f && !preg_match('/^(and|or)$/i', trim($f))) $fand[$i] = 'and';
1281 }
1282
1283 /* FilterField must be a positive integer */
1284 foreach($ffield as $ffi => $ffn){
1285 $ffield[$ffi] = max(0, intval($ffn));
1286 }
1287
1288 /* validate FilterOperator */
1289 foreach($fop as $i => $f){
1290 $fop[$i] = trim($f);
1291 if($f && !in_array(trim($f), array_keys($GLOBALS['filter_operators']))){
1292 $fop[$i] = '';
1293 }
1294 }
1295
1296 /* undo magic quotes if gpc */
1297 if($is_gpc){
1298 foreach($fvalue as $fvi => $fv){
1299 $fvalue[$fvi] = (get_magic_quotes_gpc() ? stripslashes($fv) : $fv);
1300 }
1301 }
1302
1303 /* clear fand, ffield and fop for filters having no value or no field */
1304 /* assume equal-to op and 'and' if missing */
1305 for($i = 1; $i <= datalist_filters_count * $FiltersPerGroup; $i++){
1306 if(!isset($fand[$i]) && !isset($ffield[$i]) && !isset($fop[$i]) && !isset($fvalue[$i])) continue;
1307
1308 if(($fvalue[$i] == '' && !in_array($fop[$i], array('is-empty', 'is-not-empty'))) || !$ffield[$i]){
1309 unset($ffield[$i], $fop[$i], $fvalue[$i]);
1310 if($i % $FiltersPerGroup != 1) unset($fand[$i]);
1311 }else{
1312 if(!$fand[$i]) $fand[$i] = 'and';
1313 if(!$fop[$i]) $fop[$i] = 'equal-to';
1314 }
1315 }
1316
1317 /* empty FilterAnd for empty groups or set to 'and' if empty while group not empty */
1318 for($i = 1; $i <= datalist_filters_count * $FiltersPerGroup; $i += $FiltersPerGroup){
1319 $empty_group = true;
1320
1321 for($j = $i; $j < ($i + $FiltersPerGroup); $j++){
1322 if($ffield[$j]) $empty_group = false;
1323 }
1324
1325 if($empty_group){
1326 $fand[$i] = '';
1327 continue;
1328 }
1329
1330 if(!$fand[$i]) $fand[$i] = 'and';
1331 }
1332
1333 return array($fand, $ffield, $fop, $fvalue);
1334 }
1335
1336 /**
1337 * @brief Returns HTML/JS code for displaying TV table options (hide/show columns)
1338 */
1339 function tv_tools(){
1340 global $Translation;
1341
1342 ob_start();
1343 ?>
1344
1345 <?php if($this->ShowTableHeader){ ?>
1346 <div class="pull-right flip btn-group vspacer-md tv-tools">
1347 <button title="<?php echo html_attr($Translation['hide/show columns']); ?>" type="button" class="btn btn-default tv-toggle" data-toggle="collapse" data-target="#toggle-columns-container"><i class="glyphicon glyphicon-align-justify rotate90"></i></button>
1348 </div>
1349 <?php } ?>
1350
1351 <div class="pull-right flip btn-group vspacer-md hspacer-md tv-tools">
1352 <button title="<?php echo html_attr($Translation['previous column']); ?>" type="button" class="btn btn-default tv-scroll" onclick="AppGini.TVScroll().less()"><i class="glyphicon glyphicon-step-backward"></i></button>
1353 <button title="<?php echo html_attr($Translation['next column']); ?>" type="button" class="btn btn-default tv-scroll" onclick="AppGini.TVScroll().more()"><i class="glyphicon glyphicon-step-forward"></i></button>
1354 </div>
1355 <div class="clearfix"></div>
1356
1357 <?php if($this->ShowTableHeader){ ?>
1358 <div class="collapse" id="toggle-columns-container">
1359 <div class="well pull-right flip" style="width: 100%; max-width: 600px;">
1360 <div class="row" id="toggle-columns">
1361 <div class="col-md-12">
1362 <div class="btn-group" style="width: 100%;">
1363 <button type="button" class="btn btn-default" id="show-all-columns" style="width: 33.3%;"><i class="glyphicon glyphicon-check"></i> <?php echo $Translation['Reset Filters']; ?></button>
1364 <button type="button" class="btn btn-default" id="hide-all-columns" style="width: 33.3%;"><i class="glyphicon glyphicon-unchecked"></i> <?php echo $Translation['hide all']; ?></button>
1365 <button type="button" class="btn btn-default" id="toggle-columns-checks" style="width: 33.4%;"><i class="glyphicon glyphicon-random"></i> <?php echo $Translation['toggle']; ?></button>
1366 </div>
1367 </div>
1368 <div class="col-md-12"><button type="button" class="btn btn-default btn-block" id="toggle-columns-collapser" data-toggle="collapse" data-target="#toggle-columns-container"><i class="glyphicon glyphicon-ok"></i> <?php echo $Translation['ok']; ?></button></div>
1369 </div>
1370 </div>
1371 <div class="clearfix"></div>
1372 </div>
1373 <?php } ?>
1374
1375 <script>
1376 $j(function(){
1377 /**
1378 * @brief Saves/retrieves value of column toggle status
1379 *
1380 * @param [in] col_class class of column concerned
1381 * @param [in] val boolean, optional value to save.
1382 * @return column toggle status if no value is passed
1383 */
1384 var col_cookie = function(col_class, val){
1385 if(col_class === undefined) return true;
1386 if(val !== undefined && val !== true && val !== false) val = true;
1387
1388 var cn = 'columns-' + location.pathname.split(/\//).pop().split(/\./).shift(); // cookie name
1389 var op = { expires: 30, path: '' }; // cookie options
1390 var c = Cookies.getJSON(cn) || {};
1391
1392 /* if no cookie, create it and set it to val (or true if no val) */
1393 if(c[col_class] === undefined){
1394 if(val === undefined) val = true;
1395
1396 c[col_class] = val;
1397 Cookies.set(cn, c, op);
1398 return val;
1399 }
1400
1401 /* if cookie found and val provided, set cookie to new val */
1402 if(val !== undefined){
1403 c[col_class] = val;
1404 Cookies.set(cn, c, op);
1405 return val;
1406 }
1407
1408 /* if cookie found and no val, return cookie val */
1409 return c[col_class];
1410 }
1411
1412 /**
1413 * @brief shows/hides column given its class, and saves this into cookies
1414 *
1415 * @param [in] col_class class of column to show/hide
1416 * @param [in] show boolean, optional. Set to false to hide. Default is true (to show).
1417 */
1418 var show_column = function(col_class, show){
1419 if(col_class == undefined) return;
1420 if(show == undefined) show = true;
1421
1422 if(show === false) $j('.' + col_class).hide();
1423 else $j('.' + col_class).show();
1424
1425 AppGini.TVScroll().reset();
1426
1427 col_cookie(col_class, show);
1428 }
1429
1430 /* initiate TVScroll */
1431 AppGini.TVScroll().less();
1432
1433 <?php if($this->ShowTableHeader){ ?>
1434 /* handle toggling columns' checkboxes */
1435 $j('#toggle-columns-container').on('click', 'input[type=checkbox]', function(){
1436 show_column($j(this).data('col'), $j(this).prop('checked'));
1437 });
1438
1439 /* get TV columns and populate the #toggle-columns section */
1440 $j('.table_view th').each(function(){
1441 var th = $j(this);
1442
1443 /* ignore the record selector column */
1444 if(th.find('#select_all_records').length > 0) return;
1445
1446 var col_class = th.attr('class');
1447 var label = $j.trim(th.text());
1448
1449 /* Add a toggler for the column in the #toggle-columns section */
1450 $j(
1451 '<div class="col-md-6"><div class="checkbox"><label>' +
1452 '<input type="checkbox" data-col="' + col_class + '" checked> ' + label +
1453 '</label></div></div>'
1454 ).insertBefore('#toggle-columns-collapser');
1455
1456 /* load saved column status */
1457 var col_status = col_cookie(col_class);
1458 if(col_status === false) $j('#toggle-columns input[type=checkbox]:last').trigger('click');
1459 });
1460
1461 /* handle clicking 'show all [columns]' */
1462 $j('#show-all-columns').click(function(){
1463 $j('#toggle-columns input[type=checkbox]:not(:checked)').trigger('click');
1464 });
1465
1466 /* handle clicking 'hide all [columns]' */
1467 $j('#hide-all-columns').click(function(){
1468 $j('#toggle-columns input[type=checkbox]:checked').trigger('click');
1469 });
1470
1471 /* handle clicking 'toggle [columns]' */
1472 $j('#toggle-columns-checks').click(function(){
1473 $j('#toggle-columns input[type=checkbox]').trigger('click');
1474 });
1475 <?php } ?>
1476 })
1477 </script>
1478 <?php
1479 return ob_get_clean();
1480 }
1481
1482 }